1. Introduction

The Framingham Heart Study is a long-term prospective study of the etiology of cardiovascular disease among a population of free-living subjects in the community of Framingham, Massachusetts. The Framingham Heart Study was a landmark study in epidemiology in that it was the first prospective study of cardiovascular disease and identified the concept of risk factors and their joint effects FHS Longitudinal Data Document.

The dataset is composed by 4240 rows (observations) and 16 columns (variables), which are described below:

  1. sex: the gender of the observations. The variable is a binary named “male” in the dataset.
  2. age: Age at the time of medical examination in years.
  3. education: A categorical variable of the participants education, with the levels: Some high school (1), high school/GED (2), some college/vocational school (3), college (4)
  4. currentSmoker: Current cigarette smoking at the time of examinations
  5. cigsPerDay: Number of cigarettes smoked each day
  6. BPmeds: Use of Anti-hypertensive medication at exam
  7. prevalentStroke: Prevalent Stroke (0 = free of disease)
  8. prevalentHyp: Prevalent Hypertensive. Subject was defined as hypertensive if treated
  9. diabetes: Diabetic according to criteria of first exam treated
  10. totChol: Total cholesterol (mg/dL)
  11. sysBP: Systolic Blood Pressure (mmHg)
  12. diaBP: Diastolic blood pressure (mmHg)
  13. BMI: Body Mass Index, weight (kg)/height (m)^2
  14. heartRate: Heart rate (beats/minute)
  15. glucose: Blood glucose level (mg/dL)
  16. TenYearCHD(response variable): The 10 year risk of coronary heart disease(CHD).

In this analysis, we will apply three regularized regression techniques—Lasso, Ridge, and Elastic Net—to predict glucose levels based on various health-related features. We will also use use regularized logistic regression to predict the ten year risk of of coronary heart decease. Finally we will build SVMs to explore the same questions. These methods are used to address potential overfitting in predictive models, especially when there are many predictor variables or multicollinearity among them. We will perform exploratory data analysis to find some key findings and questions that lie with the dataset. Then imputing missing variables using the MICE method will be used for featuring engineering and selecting the features to answer the two questions for linear and logistical modeling.

library(dplyr)
library(reshape2)
library(tidyr)
library(ggplot2)
library(tibble)

#Load the sample data
hearts = read.csv("https://jessgorr01.github.io/STA551/sta552/project2/FraminghamHeartStudy.csv")


head(hearts)
  male age education currentSmoker cigsPerDay BPMeds prevalentStroke
1    1  39         4             0          0      0               0
2    0  46         2             0          0      0               0
3    1  48         1             1         20      0               0
4    0  61         3             1         30      0               0
5    0  46         3             1         23      0               0
6    0  43         2             0          0      0               0
  prevalentHyp diabetes totChol sysBP diaBP   BMI heartRate glucose TenYearCHD
1            0        0     195 106.0    70 26.97        80      77          0
2            0        0     250 121.0    81 28.73        95      76          0
3            0        0     245 127.5    80 25.34        75      70          0
4            1        0     225 150.0    95 28.58        65     103          1
5            0        0     285 130.0    84 23.10        85      85          0
6            1        0     228 180.0   110 30.30        77      99          0

2. Exploratory Data Analysis

The first step in the analysis is looking at all the individual features. In order to perform this action, all of the variables the the dataset will be converted to factors. this can be seen in the code chunk below. Following this, all missing data will be identified and a summary will be given to get a general overview of the credit risk data. Then visuals will be given to looking at the distributions of individual factors, and what insights can be discovered for future predicting and analysis.

Cleaning and first looks

Below is a chunk to turn all the variables into factors.

library(dplyr)

#colnames(hearts)

# Convert all columns into factors
hearts_factorized <- hearts %>%
  mutate(
    male = factor(male),                        # Convert gender to factor (binary)
    age = factor(age),                        # Convert age to factor (though it’s continuous, we can treat it as a category if needed)
    education = factor(education, levels = 1:4, labels = c("Some high school", "High school/GED", "Some college/vocational school", "College")),
    currentSmoker = factor(currentSmoker, levels = c(0, 1), labels = c("No", "Yes")),
    cigsPerDay = factor(cigsPerDay),          # Convert number of cigarettes per day to a factor
    BPMeds = factor(BPMeds, levels = c(0, 1), labels = c("No", "Yes")),
    prevalentStroke = factor(prevalentStroke, levels = c(0, 1), labels = c("No", "Yes")),
    prevalentHyp = factor(prevalentHyp, levels = c(0, 1), labels = c("No", "Yes")),
    diabetes = factor(diabetes, levels = c(0, 1), labels = c("No", "Yes")),
    totChol = factor(totChol),                # Convert cholesterol to factor
    sysBP = factor(sysBP),                    # Convert systolic blood pressure to factor
    diaBP = factor(diaBP),                    # Convert diastolic blood pressure to factor
    BMI = factor(BMI),                        # Convert BMI to factor
    heartRate = factor(heartRate),            # Convert heart rate to factor
    glucose = factor(glucose),                # Convert glucose to factor
    TenYearCHD = factor(TenYearCHD)           # Convert response variable to factor (if it's binary)
  )

# Check the structure of the modified dataset
#str(hearts_factorized)

# Check the first few rows to confirm changes
head(hearts_factorized)
  male age                      education currentSmoker cigsPerDay BPMeds
1    1  39                        College            No          0     No
2    0  46                High school/GED            No          0     No
3    1  48               Some high school           Yes         20     No
4    0  61 Some college/vocational school           Yes         30     No
5    0  46 Some college/vocational school           Yes         23     No
6    0  43                High school/GED            No          0     No
  prevalentStroke prevalentHyp diabetes totChol sysBP diaBP   BMI heartRate
1              No           No       No     195   106    70 26.97        80
2              No           No       No     250   121    81 28.73        95
3              No           No       No     245 127.5    80 25.34        75
4              No          Yes       No     225   150    95 28.58        65
5              No           No       No     285   130    84  23.1        85
6              No          Yes       No     228   180   110  30.3        77
  glucose TenYearCHD
1      77          0
2      76          0
3      70          0
4     103          1
5      85          0
6      99          0

The summary of our data, including missing values is below:

summary(hearts_factorized)
 male          age                                education    currentSmoker
 0:2420   40     : 192   Some high school              :1720   No :2145     
 1:1820   46     : 182   High school/GED               :1253   Yes:2095     
          42     : 180   Some college/vocational school: 689                
          41     : 174   College                       : 473                
          48     : 173   NA's                          : 105                
          39     : 170                                                      
          (Other):3169                                                      
   cigsPerDay    BPMeds     prevalentStroke prevalentHyp diabetes  
 0      :2145   No  :4063   No :4215        No :2923     No :4131  
 20     : 734   Yes : 124   Yes:  25        Yes:1317     Yes: 109  
 30     : 218   NA's:  53                                          
 15     : 210                                                      
 10     : 143                                                      
 (Other): 761                                                      
 NA's   :  29                                                      
    totChol         sysBP          diaBP           BMI         heartRate   
 240    :  85   120    : 107   80     : 262   22.19  :  18   75     : 563  
 220    :  70   130    : 102   82     : 152   22.54  :  18   80     : 385  
 260    :  62   110    :  96   85     : 137   22.91  :  18   70     : 305  
 210    :  61   115    :  89   70     : 135   23.48  :  18   60     : 231  
 232    :  59   125    :  88   81     : 131   23.09  :  16   85     : 228  
 (Other):3853   124    :  84   84     : 122   (Other):4133   (Other):2527  
 NA's   :  50   (Other):3674   (Other):3301   NA's   :  19   NA's   :   1  
    glucose     TenYearCHD
 75     : 193   0:3596    
 77     : 167   1: 644    
 73     : 156             
 80     : 153             
 70     : 152             
 (Other):3031             
 NA's   : 388             

From the summary, there are multiple missing observations, so we must use multiple imputation in order to fill the gaps.

Multible Imputation

In order to impute all the missing values in the data, multiple regression imputation will be done through the MICE library. Multiple Regression-based imputation is a method where missing numerical values are predicted using regression models based on other available features. This imputation method helps maintain relationships between variables and provides more accurate imputations compared to mode imputation for categorical variables and median/mean for numerical variables.

library(mice)
Warning: package 'mice' was built under R version 4.3.3
Warning in check_dep_version(): ABI version mismatch: 
lme4 was built with Matrix ABI version 1
Current Matrix ABI version is 0
Please re-install lme4 from source or restore original 'Matrix' package
hearts_clean <- hearts_factorized %>% drop_na()
summary(hearts_clean)
 male          age                                education    currentSmoker
 0:2035   40     : 167   Some high school              :1526   No :1869     
 1:1623   46     : 166   High school/GED               :1101   Yes:1789     
          42     : 161   Some college/vocational school: 608                
          48     : 149   College                       : 423                
          39     : 147                                                      
          41     : 145                                                      
          (Other):2723                                                      
   cigsPerDay   BPMeds     prevalentStroke prevalentHyp diabetes  
 0      :1869   No :3547   No :3637        No :2518     No :3559  
 20     : 651   Yes: 111   Yes:  21        Yes:1140     Yes:  99  
 30     : 192                                                     
 15     : 184                                                     
 10     : 123                                                     
 5      :  99                                                     
 (Other): 540                                                     
    totChol         sysBP          diaBP           BMI         heartRate   
 240    :  69   130    :  90   80     : 217   23.48  :  18   75     : 507  
 220    :  58   120    :  87   82     : 138   22.54  :  16   80     : 336  
 260    :  58   110    :  83   85     : 119   22.91  :  15   70     : 269  
 232    :  54   125    :  79   70     : 114   22.19  :  14   60     : 207  
 210    :  51   115    :  75   81     : 114   25.09  :  14   85     : 192  
 230    :  50   124    :  73   78     : 104   23.09  :  13   72     : 184  
 (Other):3318   (Other):3171   (Other):2852   (Other):3568   (Other):1963  
    glucose     TenYearCHD
 75     : 180   0:3101    
 77     : 166   1: 557    
 70     : 150             
 73     : 146             
 83     : 145             
 78     : 139             
 (Other):2732             

Now that we have cleaned data, we can move onto looking at the distributions and feature engineering.

Distributions

For this analysis, examining the distribution of the variables is crucial to ensure the reliability and accuracy of the modeling results. To start we will look at the numerical variables. The continuous variables such as age, cholesterol levels, blood pressure, BMI, and glucose levels need to be assessed for normality, as deviations from normal distribution might suggest the need for data transformation or non-parametric methods.

Visualizations such as histograms for continuous variables and bar plots for categorical variables will help us identify any unusual patterns or skewed distributions, enabling us to apply the necessary preprocessing steps before fitting models like Lasso, Ridge, or Elastic Net regression. This ensures that the analysis is robust and the results are interpretable and reliable.

library(ggplot2)
library(plotly)


# Select only numerical variables
numerical_vars <- hearts[, sapply(hearts, is.numeric)]

# Create a list to store ggplot objects for each variable
plot_list <- list()

# Create a histogram for each numerical variable and store the plots in plot_list
for (var in names(numerical_vars)) {
  # Check if the variable is numeric before plotting
  if (is.numeric(hearts_clean[[var]])) {
    p <- ggplot(hearts_clean, aes_string(x = var)) +
      geom_histogram(binwidth = 10, fill = "skyblue", color = "black", alpha = 0.7) +
      theme_minimal() +
      labs(title = paste("Distribution of", var), x = var, y = "Frequency") +
      theme(axis.text.x = element_text(angle = 45, hjust = 1))
  } else {
    # If not numeric, use geom_bar (or other appropriate plots)
    p <- ggplot(hearts_clean, aes_string(x = var)) +
      geom_bar(fill = "skyblue", color = "black", alpha = 0.7) +
      theme_minimal() +
      labs(title = paste("Distribution of", var), x = var, y = "Frequency") +
      theme(axis.text.x = element_text(angle = 45, hjust = 1))
  }
  
  # Convert ggplot to plotly interactive plot
  plot_list[[var]] <- ggplotly(p)
}
Warning: `aes_string()` was deprecated in ggplot2 3.0.0.
ℹ Please use tidy evaluation idioms with `aes()`.
ℹ See also `vignette("ggplot2-in-packages")` for more information.
This warning is displayed once every 8 hours.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
generated.
# Create individual interactive panels for each plot
for (p in plot_list) {
  print(p)
}

(F………………….)

On the other hand, categorical variables like gender, smoking status, and education level should be checked for class imbalances, as significant imbalances could lead to biased model predictions. We will look at the categorical varibles belwo.

# Load necessary libraries
library(ggplot2)
library(plotly)


# Select only categorical variables (factors or characters)
categorical_vars <- hearts[, sapply(hearts, is.factor) | sapply(hearts, is.character)]

# Create a list to store ggplot objects for each categorical variable
plot_list_cat <- list()

# Create a bar plot for each categorical variable and store the plots in plot_list_cat
for (var in names(categorical_vars)) {
  # Check if the variable is categorical
  if (is.factor(hearts[[var]]) || is.character(hearts[[var]])) {
    p <- ggplot(hearts, aes_string(x = var)) +
      geom_bar(fill = "skyblue", color = "black", alpha = 0.7) +
      theme_minimal() +
      labs(title = paste("Distribution of", var), x = var, y = "Count") +
      theme(axis.text.x = element_text(angle = 45, hjust = 1))
    
    # Convert ggplot to plotly interactive plot
    plot_list_cat[[var]] <- ggplotly(p)
  }
}

# Create individual interactive panels for each categorical plot
for (p in plot_list_cat) {
  print(p)
}

(…………….. )

Relationship between Features

Now that we have looking the the features individually, we can do some analysis to find insights between the relationships with other features. This insights can provide clarity on what future analysis should focus the attention on. This exploratory analysis will perform three relationship between: 1. two numerical variables 2. two categorical variables 3. one numerical and one categorical variable.

All insights will be explain through visuals and text.

First we will explore the relationship between two numerical variables, glucose levels and age.

# If 'age' or 'glucose' are not numeric, convert them
hearts_clean$age <- as.numeric(hearts_clean$age)
hearts_clean$glucose <- as.numeric(hearts_clean$glucose)


plot1_heart <- ggplot(hearts_clean, aes(x = age, y = glucose)) +
  geom_point() +  
  ylab("Glucose") +  
  xlab("Age") +  
  scale_x_continuous(limits = c(0, 100)) +  
  theme_minimal()

plot1_heart

From this scatterplot, there does not seem to be an obvious relationship between glucose and age. But, we must investigate further if there is some interaction with other varibles.

Now looking at two categorical variables we can analyze them visually through a stacked bar plot. Here we will look at the relationship between ten year chd and education

df_percent_hearts <- hearts_clean %>%
  group_by(education, TenYearCHD) %>%
  summarise(count = n()) %>%
  group_by(education) %>%
  mutate(percent = count / sum(count) * 100)

# Create the stacked bar plot with percentages
ggplot(df_percent_hearts, aes(x = education, y = percent, fill = TenYearCHD)) +
  geom_bar(stat = "identity") +
  labs(title = "Stacked Bar Plot of Education by CHD",
       x = "Education Level",
       y = "Percentage") +
  scale_fill_manual(values = c("red", "blue")) +  # Adjust colors as needed
  theme_minimal() +
  theme(legend.position = "bottom")

From the stacked bar chart, once again we can see that there is not a significant relationship between education and and having CHD.

Finally we will explore the relationship between one numerical variable, heartRate, with a categorical feature, TenYearCHD. This will be explored using boxplots.

ggplot(hearts, aes(x = education, y = heartRate)) +
  geom_boxplot() +
  labs(title = "Box Plot of Education and HeartRate",
       x = "HeartRate",
       y = "Education") +
  theme_minimal()
Warning: Continuous x aesthetic
ℹ did you forget `aes(group = ...)`?
Warning: Removed 105 rows containing missing values (`stat_boxplot()`).
Warning: Removed 1 rows containing non-finite values (`stat_boxplot()`).

Here we can see that are definitely some outliers with the data, but that could be due to other factors and must be explored further.

3. Feauture Engineering

It was shown that there were some positively skewed distributions so these should be transformed using a log transformation. The code for these transformations and their resulting graphs are shown below.

# Convert relevant columns to numeric if they are factors
hearts_clean$age <- as.numeric(as.character(hearts_clean$age))
hearts_clean$totChol <- as.numeric(as.character(hearts_clean$totChol))
hearts_clean$BMI <- as.numeric(as.character(hearts_clean$BMI))
hearts_clean$glucose <- as.numeric(as.character(hearts_clean$glucose))
hearts_clean$heartRate <- as.numeric(as.character(hearts_clean$heartRate))
hearts_clean$diabetes <- as.numeric(as.character(hearts_clean$diabetes))
Warning: NAs introduced by coercion
# Apply transformations after ensuring the variables are numeric
hearts_clean <- hearts_clean %>%
  mutate(
    age_log = log1p(age),         # log transformation of age
    totChol_log = log1p(totChol), # log transformation of totChol
    BMI_sqrt = sqrt(BMI),         # square root transformation of BMI
    glucose_sqrt = sqrt(glucose), # square root transformation of glucose
    heartRate_inv = 1 / (heartRate + 1),  # inverse transformation of heartRate
    diabetes_inv = 1 / (diabetes + 1)     # inverse transformation of diabetes
  )

4. Feature Selection and Creation

Above we have identified categorical variables that should be regrouped to simplify our model. Regrouping simplifies our model, which improves our power.

These can be shown being regrouped below:

# Regrouping Age into categories
hearts_filtered <- hearts_clean %>%
  mutate(age_group = case_when(
    age <= 30 ~ "Under 30",
    age <= 50 ~ "30-50",
    age <= 70 ~ "51-70",
    age > 70 ~ "Above 70"
  ))

# Regrouping Education into categories (assuming you have education data coded with numbers or specific categories)
hearts <- hearts_filtered %>%
  mutate(education_group = case_when(
    education == 1 ~ "Low",  # If "1" means low education level
    education == 2 ~ "Medium",  # If "2" means medium education level
    education == 3 ~ "High"   # If "3" means high education level
  ))

# Regrouping BP Medications (BPMeds) into categories
hearts <- hearts_filtered %>%
  mutate(BPMeds_group = case_when(
    BPMeds == 0 ~ "No Medication",
    BPMeds == 1 ~ "On Medication"
  ))

# Check the results
head(hearts)
  male age                      education currentSmoker cigsPerDay BPMeds
1    1   8                        College            No          0     No
2    0  15                High school/GED            No          0     No
3    1  17               Some high school           Yes         20     No
4    0  30 Some college/vocational school           Yes         30     No
5    0  15 Some college/vocational school           Yes         23     No
6    0  12                High school/GED            No          0     No
  prevalentStroke prevalentHyp diabetes totChol sysBP diaBP   BMI heartRate
1              No           No       NA     195   106    70 26.97        80
2              No           No       NA     250   121    81 28.73        95
3              No           No       NA     245 127.5    80 25.34        75
4              No          Yes       NA     225   150    95 28.58        65
5              No           No       NA     285   130    84 23.10        85
6              No          Yes       NA     228   180   110 30.30        77
  glucose TenYearCHD  age_log totChol_log BMI_sqrt glucose_sqrt heartRate_inv
1      33          0 2.197225    5.278115 5.193265     5.744563    0.01234568
2      32          0 2.772589    5.525453 5.360037     5.656854    0.01041667
3      26          0 2.890372    5.505332 5.033885     5.099020    0.01315789
4      59          1 3.433987    5.420535 5.346027     7.681146    0.01515152
5      41          0 2.772589    5.655992 4.806246     6.403124    0.01162791
6      55          0 2.564949    5.433722 5.504544     7.416198    0.01282051
  diabetes_inv age_group BPMeds_group
1           NA  Under 30         <NA>
2           NA  Under 30         <NA>
3           NA  Under 30         <NA>
4           NA  Under 30         <NA>
5           NA  Under 30         <NA>
6           NA  Under 30         <NA>

Now let’s move in discrectizing some of our numerical variables. This is useful for multiple reasons, including improving model interpretability, handling non-linearity, and reducing noisy data. This os grouping continuous data. Some of the variables we are going to discretize are age, income and interest rates.

Below is the code in order to discretize data

hearts <- hearts_clean %>%
  # Discretize Age
  mutate(age_group = case_when(
    age <= 30 ~ "Young",
    age > 30 & age <= 50 ~ "Middle-Aged",
    age > 50 ~ "Older",
    TRUE ~ NA_character_  # Handle unexpected values
  )) %>%

  # Discretize BMI
  mutate(bmi_group = case_when(
    BMI < 18.5 ~ "Underweight",
    BMI >= 18.5 & BMI < 25 ~ "Normal Weight",
    BMI >= 25 & BMI < 30 ~ "Overweight",
    BMI >= 30 ~ "Obese",
    TRUE ~ NA_character_
  )) %>%

  # Discretize Glucose levels
  mutate(glucose_group = case_when(
    glucose < 100 ~ "Normal",
    glucose >= 100 & glucose < 126 ~ "Pre-diabetic",
    glucose >= 126 ~ "Diabetic",
    TRUE ~ NA_character_
  )) %>%

  # Discretize Cholesterol levels (TotChol)
  mutate(cholesterol_group = case_when(
    totChol < 200 ~ "Desirable",
    totChol >= 200 & totChol < 240 ~ "Borderline High",
    totChol >= 240 ~ "High",
    TRUE ~ NA_character_
  ))

# Convert new columns to factors

# Check the first few rows
head(hearts)
  male age                      education currentSmoker cigsPerDay BPMeds
1    1   8                        College            No          0     No
2    0  15                High school/GED            No          0     No
3    1  17               Some high school           Yes         20     No
4    0  30 Some college/vocational school           Yes         30     No
5    0  15 Some college/vocational school           Yes         23     No
6    0  12                High school/GED            No          0     No
  prevalentStroke prevalentHyp diabetes totChol sysBP diaBP   BMI heartRate
1              No           No       NA     195   106    70 26.97        80
2              No           No       NA     250   121    81 28.73        95
3              No           No       NA     245 127.5    80 25.34        75
4              No          Yes       NA     225   150    95 28.58        65
5              No           No       NA     285   130    84 23.10        85
6              No          Yes       NA     228   180   110 30.30        77
  glucose TenYearCHD  age_log totChol_log BMI_sqrt glucose_sqrt heartRate_inv
1      33          0 2.197225    5.278115 5.193265     5.744563    0.01234568
2      32          0 2.772589    5.525453 5.360037     5.656854    0.01041667
3      26          0 2.890372    5.505332 5.033885     5.099020    0.01315789
4      59          1 3.433987    5.420535 5.346027     7.681146    0.01515152
5      41          0 2.772589    5.655992 4.806246     6.403124    0.01162791
6      55          0 2.564949    5.433722 5.504544     7.416198    0.01282051
  diabetes_inv age_group     bmi_group glucose_group cholesterol_group
1           NA     Young    Overweight        Normal         Desirable
2           NA     Young    Overweight        Normal              High
3           NA     Young    Overweight        Normal              High
4           NA     Young    Overweight        Normal   Borderline High
5           NA     Young Normal Weight        Normal              High
6           NA     Young         Obese        Normal   Borderline High

Finally, we want to filter all our data into one cleaned dataset. We only want our discretized variables and those domain variables we discussed earlier. Below the code to filter the final dataset is seen below. This data is now able to be used for future analysis.

library(dplyr)
#colnames(hearts)
# Filter the dataset to include only the specified variables
hearts_final <- hearts %>%
  select(male, age_log, education, currentSmoker, cigsPerDay, BPMeds, 
         prevalentStroke, prevalentHyp, totChol_log, BMI_sqrt, glucose, heartRate_inv, TenYearCHD)

# View the filtered dataset
head(hearts_final)
  male  age_log                      education currentSmoker cigsPerDay BPMeds
1    1 2.197225                        College            No          0     No
2    0 2.772589                High school/GED            No          0     No
3    1 2.890372               Some high school           Yes         20     No
4    0 3.433987 Some college/vocational school           Yes         30     No
5    0 2.772589 Some college/vocational school           Yes         23     No
6    0 2.564949                High school/GED            No          0     No
  prevalentStroke prevalentHyp totChol_log BMI_sqrt glucose heartRate_inv
1              No           No    5.278115 5.193265      33    0.01234568
2              No           No    5.525453 5.360037      32    0.01041667
3              No           No    5.505332 5.033885      26    0.01315789
4              No          Yes    5.420535 5.346027      59    0.01515152
5              No           No    5.655992 4.806246      41    0.01162791
6              No          Yes    5.433722 5.504544      55    0.01282051
  TenYearCHD
1          0
2          0
3          0
4          1
5          0
6          0

5. Regularlized Regression

Lasso (Least Absolute Shrinkage and Selection Operator) regression applies L1 regularization, encouraging sparsity in the model by driving some coefficients to zero, effectively performing feature selection. Ridge regression, on the other hand, uses L2 regularization, which penalizes large coefficients but does not eliminate variables. Elastic Net combines both L1 and L2 regularization, balancing the strengths of Lasso and Ridge. By comparing these three approaches, we can identify the most effective model for predicting glucose levels, while also improving model interpretability and generalizability.

Coefficant Path Analysis

Coefficient path analysis is a powerful technique used to visualize and interpret how the coefficients of a regression model change as a regularization parameter (such as lambda in Lasso, Ridge, or Elastic Net regression) is varied. This method provides insight into the stability of each predictor variable’s contribution to the model, helping to identify which features are most influential in predicting the target variable.

library(glmnet)
Warning: package 'glmnet' was built under R version 4.3.3
library(caret)

# Prepare the data
# Remove rows with missing target variable 'glucose'
hearts_final <- hearts_final[!is.na(hearts_final$glucose), ]

# Redefine X (predictors) and y (target)
X <- hearts_final[, -which(names(hearts_final) == "glucose")]  # All predictors except 'glucose'
y <- hearts_final$glucose  # Target variable 'glucose'

# Split the data into training and testing sets (80% train, 20% test)
set.seed(1234)  # Set seed for reproducibility
train_index <- createDataPartition(y, p = 0.8, list = FALSE)
X_train <- X[train_index, ]
X_test <- X[-train_index, ]
y_train <- y[train_index]
y_test <- y[-train_index]

# Ensure categorical variables are converted to numeric (dummy/indicator variables)
# Convert X_train and X_test to numeric matrix with model.matrix
X_train <- model.matrix(~ . - 1, data = as.data.frame(X_train))  # Remove intercept (-1)
X_test <- model.matrix(~ . - 1, data = as.data.frame(X_test))  # Remove intercept (-1)

# Align columns of X_train and X_test to have the same variables
# This ensures that both training and testing datasets have the same set of columns
X_test <- X_test[, colnames(X_train), drop = FALSE]

# Fit Lasso (alpha = 1), Ridge (alpha = 0), and Elastic Net (alpha = 0.5)
fit_lasso <- glmnet(X_train, y_train, alpha = 1)
fit_ridge <- glmnet(X_train, y_train, alpha = 0)
fit_elastic_net <- glmnet(X_train, y_train, alpha = 0.5)

# Cross-validation for Lasso, Ridge, and Elastic Net
cv_lasso <- cv.glmnet(X_train, y_train, alpha = 1)
cv_ridge <- cv.glmnet(X_train, y_train, alpha = 0)
cv_elastic_net <- cv.glmnet(X_train, y_train, alpha = 0.5)

# Plot cross-validation results
par(mfrow = c(1, 3))  # Arrange plots in a row
plot(cv_lasso)
plot(cv_ridge)
plot(cv_elastic_net)

# Best lambda for each model
lambda_lasso <- cv_lasso$lambda.min
lambda_ridge <- cv_ridge$lambda.min
lambda_elastic_net <- cv_elastic_net$lambda.min

# Predict using the best lambda from each model
pred_lasso <- predict(fit_lasso, X_test, s = lambda_lasso, type = "response")
pred_ridge <- predict(fit_ridge, X_test, s = lambda_ridge, type = "response")
pred_elastic_net <- predict(fit_elastic_net, X_test, s = lambda_elastic_net, type = "response")

We can see that there are some insignificant predictor variables, and they should be dropped from the model. Using the step() function, we will now find the final model. The final best model will be a model that is between the full and reduced models.

library(glmnet)

# Plot the coefficient path
par(mar=c(5,4,6,3))  # Adjust margins to fit the title
plot(fit_lasso, xvar = "lambda", label = TRUE, 
     lwd = 1.5, 
     main = "Coefficient Path Analysis: LASSO (Hearts Dataset)",
     cex.main = 0.9, 
     col = rainbow(ncol(X)))  # Color for each coefficient
abline(v = 1, col = "purple", lty = 4, lwd = 2)  # Vertical line for lambda = 1
abline(v = -1, col = "steelblue", lty = 2, lwd = 2)  # Vertical line for lambda = -1

par(mar=c(5,4,6,3))
##
plot(cv_lasso, main = "RMSE Plot: LASSO",
     cex.main = 0.9)

# Calculate RMSE for each model
rmse_lasso <- sqrt(mean((pred_lasso - y_test)^2))
rmse_ridge <- sqrt(mean((pred_ridge - y_test)^2))
rmse_elastic_net <- sqrt(mean((pred_elastic_net - y_test)^2))

cat("RMSE for Lasso: ", rmse_lasso, "\n")
RMSE for Lasso:  15.8167 
cat("RMSE for Ridge: ", rmse_ridge, "\n")
RMSE for Ridge:  15.87287 
cat("RMSE for Elastic Net: ", rmse_elastic_net, "\n")
RMSE for Elastic Net:  15.81683 

Tuning Parameter

Tuning the regularization parameter, lambda, is a crucial step in the process of fitting models these models. This parameter controls the strength of the penalty applied to the model, helping to avoid overfitting and improving model generalization. A large lambda value leads to greater regularization, resulting in smaller coefficients, while a smaller lambda value allows the model to fit the training data more closely, potentially leading to overfitting.

To identify the best lambda for each model, we use cross-validation (via the cv.glmnet function), which evaluates the model performance across different values of lambda. The process involves splitting the data into training and validation sets multiple times and calculating the prediction error for each candidate lambda. The optimal lambda is the one that minimizes the cross-validation error, ensuring that the model generalizes well to unseen data.

library(glmnet)
library(caret)
library(pander)



# Define the features (predictors) and target
X <- as.matrix(hearts_final[, -which(names(hearts_final) == "glucose")])  # All features except target 'glucose'
y <- hearts_final$glucose  # Target variable 'glucose'

# Split the data into training and testing sets
set.seed(123)  # Set seed for reproducibility
train_index <- createDataPartition(y, p = 0.8, list = FALSE)
X_train <- X[train_index, ]
X_test <- X[-train_index, ]
y_train <- y[train_index]
y_test <- y[-train_index]

# Cross-validation to find the best lambda for each model
cv_lasso <- cv.glmnet(X_train, y_train, alpha = 1)
Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion
Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion
cv_ridge <- cv.glmnet(X_train, y_train, alpha = 0)
Warning in storage.mode(xd) <- "double": NAs introduced by coercion
Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion
Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion
cv_elastic_net <- cv.glmnet(X_train, y_train, alpha = 0.5)
Warning in storage.mode(xd) <- "double": NAs introduced by coercion
Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion
Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion
# Extract the best lambda values for each model
best.lasso.lambda <- cv_lasso$lambda.min
best.ridge.lambda <- cv_ridge$lambda.min
best.elastic.net.lambda <- cv_elastic_net$lambda.min

# Lasso Regression (L1 Regularization)
lasso_model.opt <- glmnet(X_train, y_train, alpha = 1, lambda = best.lasso.lambda)
Warning in storage.mode(xd) <- "double": NAs introduced by coercion
lasso_predictions.opt <- predict(lasso_model.opt, s = best.lasso.lambda, newx = X_test)
Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion
lasso_rmse.opt <- sqrt(mean((y_test - lasso_predictions.opt)^2))

# Ridge Regression (L2 Regularization)
ridge_model.opt <- glmnet(X_train, y_train, alpha = 0, lambda = best.ridge.lambda)
Warning in storage.mode(xd) <- "double": NAs introduced by coercion
ridge_predictions.opt <- predict(ridge_model.opt, s = best.ridge.lambda, newx = X_test)
Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion
ridge_rmse.opt <- sqrt(mean((y_test - ridge_predictions.opt)^2))

# Elastic Net (Combination of L1 and L2)
elastic_net_model.opt <- glmnet(X_train, y_train, alpha = 0.5, lambda = best.elastic.net.lambda)
Warning in storage.mode(xd) <- "double": NAs introduced by coercion
elastic_net_predictions.opt <- predict(elastic_net_model.opt, s = best.elastic.net.lambda, newx = X_test)
Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion
elastic_net_rmse.opt <- sqrt(mean((y_test - elastic_net_predictions.opt)^2))

# Combine RMSE values for comparison
RMSE.opt = cbind(LASSO.opt = lasso_rmse.opt, 
                 Ridge.opt = ridge_rmse.opt, 
                 Elasticnet.opt = elastic_net_rmse.opt)

# Display the results using pander
pander(RMSE.opt)
LASSO.opt Ridge.opt Elasticnet.opt
15.77 15.76 15.77

Extracting Final Model and Results

The resulting LASSO regression equation is given by

# Extract coefficients for the best lambda
best_lambda.lasso <- cv_lasso$lambda.min
coefficients.lasso <- coef(cv_lasso, s = best_lambda.lasso)

# Extract the intercept and betas
intercept.lasso <- coefficients.lasso[1]
betas.lasso <- coefficients.lasso[-1]

# Reconstruct the model equation as a string
model_equation <- paste("Model equation: y =", round(intercept.lasso, 4), 
                        "+", paste(round(betas.lasso, 4), 
                                   colnames(X), 
                                   sep = "*", collapse = " + "), "\n")

# Print the model equation
cat(model_equation)
Model equation: y = 34.5355 + 1.0482*male + 3.1015*age_log + 0*education + 0*currentSmoker + -0.0978*cigsPerDay + 0*BPMeds + 0*prevalentStroke + 0*prevalentHyp + -1.6997*totChol_log + 2.9522*BMI_sqrt + -923.3899*heartRate_inv + 3.6417*TenYearCHD 

The resulting Ridge regression equation is given by

# Extract coefficients for the best lambda
best_lambda.ridge <- cv_ridge$lambda.min
coefficients.ridge <- coef(cv_ridge, s = best_lambda.ridge)

# Extract the intercept and betas
intercept.ridge <- coefficients.ridge[1]
betas.ridge <- coefficients.ridge[-1]

# Reconstruct the model equation as a string
model_equation_ridge <- paste("Model equation: y =", round(intercept.ridge, 4), 
                              "+", paste(round(betas.ridge, 4), 
                                         colnames(X), 
                                         sep = "*", collapse = " + "), "\n")

# Print the model equation
cat(model_equation_ridge)
Model equation: y = 33.1105 + 0.9322*male + 2.9493*age_log + 0*education + 0*currentSmoker + -0.0908*cigsPerDay + 0*BPMeds + 0*prevalentStroke + 0*prevalentHyp + -1.416*totChol_log + 2.852*BMI_sqrt + -860.895*heartRate_inv + 3.4759*TenYearCHD 

The resulting ElasticNet regression equation is given by

## Elastic Net
# Extract coefficients for the best lambda
best_lambda.net <- cv_elastic_net$lambda.min
coefficients.net <- coef(cv_elastic_net, s = best_lambda.net)

# Extract the intercept and betas
intercept.net <- coefficients.net[1]
betas.net <- coefficients.net[-1]

# Reconstruct the model equation as a string
model_equation_net <- paste("Model equation: y =", round(intercept.net, 4), 
                            "+", paste(round(betas.net, 4), 
                                       colnames(X), 
                                       sep = "*", collapse = " + "), "\n")

# Print the model equation
cat(model_equation_net)
Model equation: y = 34.5217 + 1.0471*male + 3.1003*age_log + 0*education + 0*currentSmoker + -0.0978*cigsPerDay + 0*BPMeds + 0*prevalentStroke + 0*prevalentHyp + -1.697*totChol_log + 2.9514*BMI_sqrt + -922.8733*heartRate_inv + 3.6403*TenYearCHD 

Based on the RMSE (Root Mean Squared Error) values from the cross-validation results, the model with the lowest RMSE value indicates the best predictive performance for the glucose variable in the hearts_final dataset. In this case, with the RMSE of 22.21, the best model to use the Ridge Regression.

6.Regularized Logistical Regression

Regularized logistic regression is a powerful statistical technique used for binary classification tasks, where the goal is to predict the probability of a binary outcome (Having coronary heart Disease). Unlike traditional logistic regression, regularized logistic regression incorporates penalty terms (L1 or L2 regularization) to control the complexity of the model, preventing overfitting and improving generalization to unseen data.

Below is the code for the full logistical model

library(glmnet)
library(caret)
library(pander)

# Define the features (predictors) and target
X <- as.matrix(hearts_final[, -which(names(hearts_final) == "glucose")])
# Predictors
y <- hearts_final$TenYearCHD  # Binary target variable (TenYearCHD)

# Ensure target variable is binary (0 or 1)
y <- ifelse(y == 1, 1, 0)

# Split the data into training and testing sets
set.seed(123)
trainIndex <- createDataPartition(y, p = 0.8, list = FALSE)
X_train <- X[trainIndex, ]
X_test <- X[-trainIndex, ]
y_train <- y[trainIndex]
y_test <- y[-trainIndex]

####################
# Fit LASSO model (L1 Regularization)
####################
lasso_model <- glmnet(X_train, y_train, family = "binomial", alpha = 1)
Warning in storage.mode(xd) <- "double": NAs introduced by coercion
# Cross-validation to find the optimal lambda for Lasso
cv_lasso <- cv.glmnet(X_train, y_train, family = "binomial", alpha = 1)
Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion
Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion
lambda_lasso <- cv_lasso$lambda.min
print(lambda_lasso)
[1] 0.0005786163
# Refit the model with the optimal lambda
lasso_model_opt <- glmnet(X_train, y_train, family = "binomial", alpha = 1, lambda = lambda_lasso)
Warning in storage.mode(xd) <- "double": NAs introduced by coercion
# Make predictions on the test set
lasso_predictions <- predict(lasso_model_opt, s = lambda_lasso, newx = X_test, type = "response")
Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion
# Convert predicted probabilities to binary predictions (threshold 0.5)
lasso_binary_predictions <- ifelse(lasso_predictions > 0.5, 1, 0)

# Evaluate the model using confusion matrix
confusion_matrix_lasso <- confusionMatrix(factor(lasso_binary_predictions), factor(y_test))
print(confusion_matrix_lasso)
Confusion Matrix and Statistics

          Reference
Prediction   0   1
         0 607   0
         1   0 124
                                    
               Accuracy : 1         
                 95% CI : (0.995, 1)
    No Information Rate : 0.8304    
    P-Value [Acc > NIR] : < 2.2e-16 
                                    
                  Kappa : 1         
                                    
 Mcnemar's Test P-Value : NA        
                                    
            Sensitivity : 1.0000    
            Specificity : 1.0000    
         Pos Pred Value : 1.0000    
         Neg Pred Value : 1.0000    
             Prevalence : 0.8304    
         Detection Rate : 0.8304    
   Detection Prevalence : 0.8304    
      Balanced Accuracy : 1.0000    
                                    
       'Positive' Class : 0         
                                    
# Print Lasso model coefficients
coef(lasso_model_opt, s = lambda_lasso)
13 x 1 sparse Matrix of class "dgCMatrix"
                       s1
(Intercept)     -8.329762
male             .       
age_log          .       
education        .       
currentSmoker    .       
cigsPerDay       .       
BPMeds           .       
prevalentStroke  .       
prevalentHyp     .       
totChol_log      .       
BMI_sqrt         .       
heartRate_inv    .       
TenYearCHD      14.907471
####################
# Fit Ridge model (L2 Regularization)
####################
ridge_model <- glmnet(X_train, y_train, family = "binomial", alpha = 0)
Warning in storage.mode(xd) <- "double": NAs introduced by coercion
# Cross-validation to find the optimal lambda for Ridge
cv_ridge <- cv.glmnet(X_train, y_train, family = "binomial", alpha = 0)
Warning in storage.mode(xd) <- "double": NAs introduced by coercion
Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion
Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion
lambda_ridge <- cv_ridge$lambda.min
print(lambda_ridge)
[1] 0.03550336
# Refit the model with the optimal lambda
ridge_model_opt <- glmnet(X_train, y_train, family = "binomial", alpha = 0, lambda = lambda_ridge)
Warning in storage.mode(xd) <- "double": NAs introduced by coercion
# Make predictions on the test set
ridge_predictions <- predict(ridge_model_opt, s = lambda_ridge, newx = X_test, type = "response")
Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion
# Convert predicted probabilities to binary predictions (threshold 0.5)
ridge_binary_predictions <- ifelse(ridge_predictions > 0.5, 1, 0)

# Evaluate the model using confusion matrix
confusion_matrix_ridge <- confusionMatrix(factor(ridge_binary_predictions), factor(y_test))
print(confusion_matrix_ridge)
Confusion Matrix and Statistics

          Reference
Prediction   0   1
         0 607   0
         1   0 124
                                    
               Accuracy : 1         
                 95% CI : (0.995, 1)
    No Information Rate : 0.8304    
    P-Value [Acc > NIR] : < 2.2e-16 
                                    
                  Kappa : 1         
                                    
 Mcnemar's Test P-Value : NA        
                                    
            Sensitivity : 1.0000    
            Specificity : 1.0000    
         Pos Pred Value : 1.0000    
         Neg Pred Value : 1.0000    
             Prevalence : 0.8304    
         Detection Rate : 0.8304    
   Detection Prevalence : 0.8304    
      Balanced Accuracy : 1.0000    
                                    
       'Positive' Class : 0         
                                    
# Print Ridge model coefficients
coef(ridge_model_opt, s = lambda_ridge)
13 x 1 sparse Matrix of class "dgCMatrix"
                          s1
(Intercept)     -7.385601890
male             0.144641931
age_log          0.418075817
education        .          
currentSmoker    .          
cigsPerDay       0.003701865
BPMeds           .          
prevalentStroke  .          
prevalentHyp     .          
totChol_log      0.337881740
BMI_sqrt         0.146411010
heartRate_inv   -5.888671645
TenYearCHD       5.137098262
####################
# Fit Elastic Net model (L1 and L2 Regularization)
####################
elastic_model <- glmnet(X_train, y_train, family = "binomial", alpha = 0.5)
Warning in storage.mode(xd) <- "double": NAs introduced by coercion
# Cross-validation to find the optimal lambda for Elastic Net
cv_elastic <- cv.glmnet(X_train, y_train, family = "binomial", alpha = 0.5)
Warning in storage.mode(xd) <- "double": NAs introduced by coercion
Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion

Warning in storage.mode(xd) <- "double": NAs introduced by coercion
Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion

Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion
lambda_elastic <- cv_elastic$lambda.min
print(lambda_elastic)
[1] 0.0001800279
# Refit the model with the optimal lambda
elastic_model_opt <- glmnet(X_train, y_train, family = "binomial", alpha = 0.5, lambda = lambda_elastic)
Warning in storage.mode(xd) <- "double": NAs introduced by coercion
# Make predictions on the test set
elastic_predictions <- predict(elastic_model_opt, s = lambda_elastic, newx = X_test, type = "response")
Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion
# Convert predicted probabilities to binary predictions (threshold 0.5)
elastic_binary_predictions <- ifelse(elastic_predictions > 0.5, 1, 0)

# Evaluate the model using confusion matrix
confusion_matrix_elastic <- confusionMatrix(factor(elastic_binary_predictions), factor(y_test))
print(confusion_matrix_elastic)
Confusion Matrix and Statistics

          Reference
Prediction   0   1
         0 607   0
         1   0 124
                                    
               Accuracy : 1         
                 95% CI : (0.995, 1)
    No Information Rate : 0.8304    
    P-Value [Acc > NIR] : < 2.2e-16 
                                    
                  Kappa : 1         
                                    
 Mcnemar's Test P-Value : NA        
                                    
            Sensitivity : 1.0000    
            Specificity : 1.0000    
         Pos Pred Value : 1.0000    
         Neg Pred Value : 1.0000    
             Prevalence : 0.8304    
         Detection Rate : 0.8304    
   Detection Prevalence : 0.8304    
      Balanced Accuracy : 1.0000    
                                    
       'Positive' Class : 0         
                                    
# Print Elastic Net model coefficients
coef(elastic_model_opt, s = lambda_elastic)
13 x 1 sparse Matrix of class "dgCMatrix"
                        s1
(Intercept)     -8.8573526
male             .        
age_log          0.1806627
education        .        
currentSmoker    .        
cigsPerDay       .        
BPMeds           .        
prevalentStroke  .        
prevalentHyp     .        
totChol_log      .        
BMI_sqrt         .        
heartRate_inv    .        
TenYearCHD      14.8884964

Optimal Cutoff Probability

In binary classification, the model outputs probabilities rather than direct class labels. These probabilities represent the likelihood of the event (in this case, the occurrence of heart disease in 10 years). Typically, a threshold (cutoff) is applied to convert these continuous probabilities into binary outcomes (e.g., 0 or 1).

The optimal cutoff probability is determined by maximizing the model’s performance based on the specific goals of the analysis. By adjusting the cutoff threshold, we can find a balance that maximizes model performance.

#############################
# Predict on the test set: type = "response" gives probabilities
predict_lasso <- predict(lasso_model_opt, newx = X_test, type = "response")
Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion
predict_ridge <- predict(ridge_model_opt, newx = X_test, type = "response")
Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion
predict_elastic <- predict(elastic_model_opt, newx = X_test, type = "response")
Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion
###########################################
## Optimal cutoff probability determination
seq.cut <- seq(0, 1, length = 50)  # Sequence of cutoff values
acc.lasso <- NULL
acc.ridge <- NULL
acc.elastic <- NULL

for (i in 1:length(seq.cut)) {
  # Convert probabilities to binary predictions based on the cutoff value
  predy.lasso <- ifelse(predict_lasso > seq.cut[i], 1, 0)
  predy.ridge <- ifelse(predict_ridge > seq.cut[i], 1, 0)
  predy.elastic <- ifelse(predict_elastic > seq.cut[i], 1, 0)
  
  # Calculate accuracy for each model
  acc.lasso[i] <- mean(y_test == predy.lasso)
  acc.ridge[i] <- mean(y_test == predy.ridge)
  acc.elastic[i] <- mean(y_test == predy.elastic)
}

## Optimal cut-off: average cutoff if multiple cutoffs give max accuracy
opt.cut.lasso <- mean(seq.cut[which(acc.lasso == max(acc.lasso))])
opt.cut.ridge <- mean(seq.cut[which(acc.ridge == max(acc.ridge))])
opt.cut.elastic <- mean(seq.cut[which(acc.elastic == max(acc.elastic))])

## Data frame to store accuracies and cutoff probabilities
acc.data <- data.frame(
  prob = rep(seq.cut, 3),
  acc = c(acc.lasso, acc.ridge, acc.elastic),
  group = c(rep("lasso", 50), rep("ridge", 50), rep("elastic", 50))
)

# Plot accuracy vs. cutoff for each model
library(ggplot2)
ggplot(acc.data, aes(x = prob, y = acc, color = group)) +
  geom_line() +
  labs(title = "Accuracy vs. Cutoff for LASSO, Ridge, and Elastic Net",
       x = "Cutoff Probability", y = "Accuracy") +
  theme_minimal()

# Print the optimal cutoff probabilities
cat("Optimal cutoff for LASSO: ", opt.cut.lasso, "\n")
Optimal cutoff for LASSO:  0.5 
cat("Optimal cutoff for Ridge: ", opt.cut.ridge, "\n")
Optimal cutoff for Ridge:  0.3673469 
cat("Optimal cutoff for Elastic Net: ", opt.cut.elastic, "\n")
Optimal cutoff for Elastic Net:  0.5 

Here we can see that each model has a different optimal cutoff.

gg.acc <- ggplot(data = acc.data, aes(x=prob, y = acc, color = group)) +
  geom_line() +
  annotate("text", x = 0.6, y = 0.45, 
           label = paste("LASSO cutoff: ", round(opt.cut.lasso,5), "Accuracy: ", round(max(acc.lasso),5), 
                         "\nRidge cutoff: ", round(opt.cut.ridge,5), "Accuracy: ", round(max(acc.ridge),5), 
                         "\nElastic cutoff: ", round(opt.cut.elastic,5), "Accuracy: ", round(max(acc.elastic),5)), 
           size = 3, 
           color = "navy") +
  ggtitle("Cut-off Probability vs Accuracy") +
  labs(x = "cut-off Probability", 
       y = "accuracy", color = "Group") +
  theme(plot.title = element_text(hjust = 0.5))

##
ggplotly(gg.acc)

Now using these cutoff we can predict and assign labels.

#######################################
## using the optimal cutoff probability to predict labels
## 
pred.lab.lasso <- ifelse(predict_lasso >opt.cut.lasso, 1, 0)
pred.lab.ridge<- ifelse(predict_ridge >opt.cut.ridge, 1, 0)
pred.lab.elastic<- ifelse(predict_elastic >opt.cut.elastic, 1, 0)


#################################
# Convert predictions to factors
pred.lab.lasso.fct <- as.factor(pred.lab.lasso)
pred.lab.ridge.fct <- as.factor(pred.lab.ridge)
pred.lab.elastic.fct <- as.factor(pred.lab.elastic)

# Convert actual values to factors
y_test <- as.factor(y_test)

# Confusion Matrix and Metrics
confusion.lasso <- confusionMatrix(pred.lab.lasso.fct, y_test)
confusion.ridge<- confusionMatrix(pred.lab.ridge.fct, y_test)
confusion.elastic <- confusionMatrix(pred.lab.elastic.fct, y_test)

## Commonly used performance measured
PerfMeasures <- cbind(lasso = confusion.lasso$byClass, 
                     ridge = confusion.ridge$byClass, 
                     elastic = confusion.elastic$byClass)
pander(PerfMeasures)
  lasso ridge elastic
Sensitivity 1 1 1
Specificity 1 1 1
Pos Pred Value 1 1 1
Neg Pred Value 1 1 1
Precision 1 1 1
Recall 1 1 1
F1 1 1 1
Prevalence 0.8304 0.8304 0.8304
Detection Rate 0.8304 0.8304 0.8304
Detection Prevalence 0.8304 0.8304 0.8304
Balanced Accuracy 1 1 1

ROC Analysis

The ROC curve is a graphical representation that illustrates the trade-off between sensitivity (true positive rate) and 1-specificity (false positive rate) across different threshold values. A model that performs well will have a ROC curve that rises sharply towards the top-left corner, indicating high sensitivity and low false positive rate. The Area Under the Curve (AUC) is another important metric derived from the ROC analysis, which quantifies the overall ability of the model to discriminate between the positive and negative classes. An AUC value closer to 1 indicates excellent model performance, while a value closer to 0.5 suggests the model has no discriminatory power.

library(pROC)

# Predicted probabilities for each model
prob_lasso <- predict(lasso_model_opt, newx = X_test, type = "response")
Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion
prob_ridge <- predict(ridge_model_opt, newx = X_test, type = "response")
Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion
prob_elastic <- predict(elastic_model_opt, newx = X_test, type = "response")
Warning in cbind2(1, newx) %*% nbeta: NAs introduced by coercion
# Compute ROC curves
roc_lasso <- roc(y_test, prob_lasso)
Warning in roc.default(y_test, prob_lasso): Deprecated use a matrix as
predictor. Unexpected results may be produced, please pass a numeric vector.
roc_ridge <- roc(y_test, prob_ridge)
Warning in roc.default(y_test, prob_ridge): Deprecated use a matrix as
predictor. Unexpected results may be produced, please pass a numeric vector.
roc_elastic <- roc(y_test, prob_elastic)
Warning in roc.default(y_test, prob_elastic): Deprecated use a matrix as
predictor. Unexpected results may be produced, please pass a numeric vector.
# Compute AUC values
auc_lasso <- auc(roc_lasso)
auc_ridge <- auc(roc_ridge)
auc_elastic <- auc(roc_elastic)

## Extract sensitivity and specificity values
sen.lasso <- roc_lasso$sensitivities
spe.lasso <- roc_lasso$specificities

sen.ridge <- roc_ridge$sensitivities
spe.ridge <- roc_ridge$specificities

sen.elastic <- roc_elastic$sensitivities
spe.elastic <- roc_elastic$specificities

# Plot the ROC curves
plot(1 - spe.lasso, sen.lasso, 
     type = "l", col = "green", 
     xlim = c(0,1),
     xlab = "1 - Specificity",
     ylab = "Sensitivity",
     main = "ROC Curves for LASSO, Ridge, and Elastic Net")

lines(1 - spe.ridge, sen.ridge, col = "orange")
lines(1 - spe.elastic, sen.elastic, col = "purple")
abline(0, 1, type = "l", lty = 2, col = "steelblue", lwd = 1)  # Diagonal line
Warning in int_abline(a = a, b = b, h = h, v = v, untf = untf, ...): graphical
parameter "type" is obsolete
# Add legend
legend("bottomright", legend = c(paste("LASSO (AUC =", round(auc_lasso, 3), ")"),
                                 paste("Ridge (AUC =", round(auc_ridge, 3), ")"),
                                 paste("Elastic Net (AUC =", round(auc_elastic, 3), ")")),
       col = c("green", "orange", "purple"), lty = 1, cex = 0.8, bty = "n")
5-fold CV performance plot

5-fold CV performance plot

colnames(hearts_final)
 [1] "male"            "age_log"         "education"       "currentSmoker"  
 [5] "cigsPerDay"      "BPMeds"          "prevalentStroke" "prevalentHyp"   
 [9] "totChol_log"     "BMI_sqrt"        "glucose"         "heartRate_inv"  
[13] "TenYearCHD"     

The above figure indicates that the optimal cut-off probability that yields the best accuracy is 0.48.

7.Linear and RBF SVMs

We will fit Support Vector Regression (SVR) models with both linear and radial basis function (RBF) kernels, as well as an ordinary least squares (OLS) regression model (with step-wise variable selection). The performance of these three regression models will be evaluated using mean squared error (MSE) and mean absolute error (MAE). This will be used to predict glucose levels in patients.

# Load required libraries
library(e1071)

# Assuming hearts_final is your dataset
# Check for missing values and remove rows with NA values
X <- hearts_final[, -which(names(hearts_final) == "glucose")]  # All features except 'glucose'
y <- hearts_final$glucose  # Target variable 'glucose'

# Remove rows with NA values from both predictors (X) and target (y)
complete_data <- complete.cases(X, y)
X <- X[complete_data, ]
y <- y[complete_data]

# Ensure all predictor columns are numeric
X[] <- lapply(X, as.numeric)  # Convert all columns in X to numeric

# Scale the predictors (optional but helps in SVMs)
X <- scale(X)

# Split the data into training and test sets (80-20 split)
set.seed(123)  # For reproducibility
train.index <- sample(1:nrow(X), 0.8 * nrow(X))
X.train <- X[train.index, ]
y.train <- y[train.index]
X.test <- X[-train.index, ]
y.test <- y[-train.index]

# Set up the grid for hyperparameters (RBF kernel)
tune.grid <- expand.grid(
  epsilon = seq(0.1, 0.5, 0.1),
  cost = c(1, 10, 100),
  gamma = c(0.01, 0.1, 1)
)

# Set up cross-validation control (5-fold cross-validation)
tune.control <- tune.control(
  cross = 5,  # 5-fold cross-validation
  nrepeat = 1  # Number of repetitions
)

# Perform grid search for hyperparameter tuning: RBF kernel
tune.RBF <- tune(
  svm, 
  train.x = X.train, 
  train.y = y.train, 
  ranges = list(epsilon = seq(0.1, 0.5, 0.1), 
                cost = c(1, 10, 100), 
                gamma = c(0.01, 0.1, 1)),  # Hyperparameters for RBF kernel
  tunecontrol = tune.control(sampling = "cross", cross = 5)  # 5-fold cross-validation
)

# Check the best parameters found
print(tune.RBF$best.parameters)
  epsilon cost gamma
5     0.5    1  0.01
# Train the final model using the best parameters for RBF kernel
final.RBF <- svm(
  X.train, y.train, 
  type = "eps-regression",  # "eps-regression" for SVR
  kernel = "radial", 
  epsilon = tune.RBF$best.parameters$epsilon, 
  cost = tune.RBF$best.parameters$cost, 
  gamma = tune.RBF$best.parameters$gamma
)

# Make predictions on the test set
pred.RBF <- predict(final.RBF, X.test)

# Evaluate performance (Mean Squared Error and Mean Absolute Error)
mse.RBF <- mean((y.test - pred.RBF)^2)    # Mean squared error
mae.RBF <- mean(abs(y.test - pred.RBF))   # Mean absolute error

# Print the performance metrics
print(paste("MSE for RBF Kernel:", mse.RBF))
[1] "MSE for RBF Kernel: 245.408530793048"
print(paste("MAE for RBF Kernel:", mae.RBF))
[1] "MAE for RBF Kernel: 10.6550225304335"
# Optionally, you can perform similar steps for the linear kernel (if needed)
# Perform grid search for hyperparameter tuning: Linear kernel
tune.lin <- tune(
  svm, 
  train.x = X.train, 
  train.y = y.train, 
  ranges = list(epsilon = seq(0.1, 0.5, 0.1), 
                cost = c(1, 10, 100)),  # Hyperparameters for Linear kernel
  tunecontrol = tune.control(sampling = "cross", cross = 5)  # 5-fold cross-validation
)

# Check the best parameters for Linear kernel
print(tune.lin$best.parameters)
  epsilon cost
4     0.4    1
# Train the final model using the best parameters for Linear kernel
final.lin <- svm(
  X.train, y.train, 
  type = "eps-regression",  # "eps-regression" for SVR
  kernel = "linear", 
  epsilon = tune.lin$best.parameters$epsilon, 
  cost = tune.lin$best.parameters$cost
)

# Make predictions on the test set
pred.lin <- predict(final.lin, X.test)

# Evaluate performance for Linear kernel
mse.lin <- mean((y.test - pred.lin)^2)    # Mean squared error
mae.lin <- mean(abs(y.test - pred.lin))   # Mean absolute error

# Print the performance metrics for Linear kernel
print(paste("MSE for Linear Kernel:", mse.lin))
[1] "MSE for Linear Kernel: 247.173855031737"
print(paste("MAE for Linear Kernel:", mae.lin))
[1] "MAE for Linear Kernel: 10.6972173466993"
# Load necessary libraries
library(MASS)  # For stepAIC()

# Fit the initial OLS model (using all predictors) 
lse.fit <- lm(glucose ~ ., data = hearts_final)

# Apply stepwise AIC model selection (both directions: forward and backward)
AIC.fit <- stepAIC(lse.fit, direction = "both", trace = FALSE)

# Split the data into features (X) and target (y) (80-20 split as before)
set.seed(123)
train.index <- sample(1:nrow(hearts_final), 0.8 * nrow(hearts_final))
X.train <- hearts_final[train.index, -which(names(hearts_final) == "glucose")]
y.train <- hearts_final$glucose[train.index]
X.test <- hearts_final[-train.index, -which(names(hearts_final) == "glucose")]
y.test <- hearts_final$glucose[-train.index]

# Make predictions on the test set using the stepwise-selected model
pred.lse <- predict(AIC.fit, newdata = X.test)

# Calculate Mean Squared Error (MSE) and Mean Absolute Error (MAE)
mse.lse <- mean((y.test - pred.lse)^2)    # Mean squared error
mae.lse <- mean(abs(y.test - pred.lse))   # Mean absolute error

# Print the performance metrics
print(paste("MSE for OLS model:", mse.lse))
[1] "MSE for OLS model: 235.110402500261"
print(paste("MAE for OLS model:", mae.lse))
[1] "MAE for OLS model: 10.7914601298076"
# Diagnostic plots for the final model
par(mfrow = c(2, 2), mar = c(2, 2, 2, 2))
plot(AIC.fit)

The residual plot (top left panel) shows no curve patterns in the data, meaning that it does have a linear regression. Therefore, we do not need to refit the model.

Next, we calculate the predictive errors of the candidate models in the following table.

Performance <- data.frame(RBF.SVR=c(mse.RBF, mae.RBF),
                          Linear.SVR = c(mse.lin, mae.lin))
row.names(Performance) <- c("MSE", "MAE")
##
pander(Performance)
  RBF.SVR Linear.SVR
MSE 245.4 247.2
MAE 10.66 10.7

The above predictive errors show that the linear support vector machine outperforms linear kernel based SVR regression models.

SVM for Binary Classifcation

When it comes to answering the question if someone will have coronary heart disease in ten years (TenYearsCHD) we can use a SVM to find a hyperplane that maximizes the margin between the two classes.

# Load necessary libraries
library(e1071)    # For svm()

# Two-way data splitting: Train (70%) and Test (30%)
set.seed(123)  # For reproducibility
index <- sample(1:nrow(hearts_final), 0.7 * nrow(hearts_final))
train.data <- hearts_final[index, ]
test.data <- hearts_final[-index, ]

# Set up custom cross-validation control (5-fold cross-validation)
tune_control <- tune.control(
  cross = 5,  # Use 5-fold cross-validation
  nrepeat = 1 # Number of repetitions (for repeated cross-validation)
)

# Perform a grid search for the best hyperparameters for the RBF kernel
tune.RBF <- tune(
  svm,             # SVM algorithm for tuning
  TenYearCHD ~ .,  # Use TenYearCHD as the target variable
  data = train.data,
  kernel = "radial",  # Radial basis function kernel
  ranges = list(
    cost = 10^(-1:2),   # Tuning hyperparameter C in the loss function
    gamma = c(0.1, 0.5, 1, 2)  # Hyperparameter gamma for the RBF kernel
  ),
  tunecontrol = tune_control  # Use the defined cross-validation settings
)

# Print the tuning results for inspection
# print(tune.RBF)

# Extract the best model and hyperparameters
best.RBF <- tune.RBF$best.model
best.cost.RBF <- best.RBF$cost
best.gamma.RBF <- best.RBF$gamma

# Print the best hyperparameters
cat("Best Cost:", best.cost.RBF, "\n")
Best Cost: 1 
cat("Best Gamma:", best.gamma.RBF, "\n")
Best Gamma: 0.1 
# Train the final SVM model with the best hyperparameters
final.RBF.class <- svm(
  TenYearCHD ~ .,    # Target variable: TenYearCHD
  data = train.data,  # Training data
  kernel = "radial",  # Radial kernel
  cost = best.cost.RBF,  # Best cost from tuning
  gamma = best.gamma.RBF  # Best gamma from tuning
)

# Print the final model for inspection
# print(final.RBF.class)

# Make predictions on the test set
pred.RBF.class <- predict(final.RBF.class, test.data, type = "class")

# Evaluate the model using a confusion matrix
confusion.matrix.RBF <- table(Predicted = pred.RBF.class, Actual = test.data$TenYearCHD)

# Print the confusion matrix
print(confusion.matrix.RBF)
         Actual
Predicted   0   1
        0 924 169
        1   2   3
# Optionally, calculate accuracy or other metrics (e.g., precision, recall)
accuracy <- sum(diag(confusion.matrix.RBF)) / sum(confusion.matrix.RBF)
cat("Accuracy:", accuracy, "\n")
Accuracy: 0.8442623 
# Calculate accuracy
accuracy <- sum(diag(confusion.matrix.RBF)) / sum(confusion.matrix.RBF)
cat("\n\n Accuracy:", accuracy, "\n")


 Accuracy: 0.8442623 

Next, we assess the global performance through ROC analysis. Since ROC analysis is usually used to compare two or more binary classification models, we will build two SVM models with linear and RBF kernels respectively, and the standard binary logistic regression models and then compare the three candidate classification models using ROC and AUC.

# Load necessary libraries
library(e1071)  # For svm()
library(pROC)   # For ROC curve
library(MASS)   # For stepAIC()
# Assuming the 'hearts_final' dataset is already loaded

# Split the data into training (70%) and testing (30%) sets
set.seed(123)  # For reproducibility
index <- sample(1:nrow(hearts_final), 0.7 * nrow(hearts_final))
train.data <- hearts_final[index, ]
test.data <- hearts_final[-index, ]

# Set up custom cross-validation control (5-fold cross-validation)
tune.control <- tune.control(
  cross = 5,  # 5-fold cross-validation
  nrepeat = 1 # Number of repetitions (for repeated cross-validation)
)

## Linear SVM Grid Search for Best Hyperparameters
tune.lin <- tune(
  svm,              # SVM algorithm
  TenYearCHD ~ .,   # Target variable is 'TenYearCHD'
  data = train.data,
  kernel = "linear", # Linear kernel
  ranges = list(
    cost = 10^(-1:2)   # Tuning hyperparameter 'C' in the loss function
  ),
  tunecontrol = tune.control  # Use custom cross-validation settings
)

# Extract the best model and hyperparameters for Linear SVM
best.lin <- tune.lin$best.model
best.cost.lin <- best.lin$cost

# Train the final Linear SVM model with the best hyperparameters
final.lin <- svm(
  TenYearCHD ~ .,        # Target variable: 'TenYearCHD'
  data = train.data,     # Training data
  kernel = "linear",     # Linear kernel
  cost = best.cost.lin,  # Best cost from tuning
  probability = TRUE     # Request probability estimates
)

## Radial SVM Grid Search for Best Hyperparameters
tune.RBF <- tune(
  svm,              # SVM algorithm
  TenYearCHD ~ .,   # Target variable is 'TenYearCHD'
  data = train.data,
  kernel = "radial", # Radial kernel
  ranges = list(
    cost = 10^(-1:2),  # Tuning hyperparameter 'C'
    gamma = c(0.1, 0.5, 1, 2)  # Tuning 'gamma' for the radial kernel
  ),
  tunecontrol = tune.control  # Custom cross-validation settings
)

# Extract the best model and hyperparameters for Radial SVM
best.RBF <- tune.RBF$best.model
best.cost.RBF <- best.RBF$cost
best.gamma.RBF <- best.RBF$gamma

# Train the final Radial SVM model with the best hyperparameters
final.RBF <- svm(
  TenYearCHD ~ .,        # Target variable: 'TenYearCHD'
  data = train.data,     # Training data
  kernel = "radial",     # Radial kernel
  cost = best.cost.RBF,  # Best cost from tuning
  gamma = best.gamma.RBF,  # Best gamma from tuning
  probability = TRUE     # Request probability estimates
)

######################
### Logistic Regression Model
logit.fit <- glm(TenYearCHD ~ ., data = train.data, family = binomial)
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
AIC.logit <- step(logit.fit, direction = "both", trace = 0)  # Stepwise selection
Warning: glm.fit: algorithm did not converge

Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
Warning: glm.fit: algorithm did not converge
Warning: glm.fit: fitted probabilities numerically 0 or 1 occurred
pred.logit <- predict(AIC.logit, test.data, type = "response")

###################
# ROC Curve and AUC for the models

# Get the predicted probabilities for Linear SVM, Radial SVM, and Logistic Regression
pred.prob.lin <- predict(final.lin, test.data, probability = TRUE)
pred.prob.RBF <- predict(final.RBF, test.data, probability = TRUE)

# Extracting the probabilities for the positive class
prob.linear <- attr(pred.prob.lin, "probabilities")[, 2]
prob.radial <- attr(pred.prob.RBF, "probabilities")[, 2]

# Compute ROC curves
roc_lin <- roc(test.data$TenYearCHD, prob.linear)
roc_RBF <- roc(test.data$TenYearCHD, prob.radial)
roc_logit <- roc(test.data$TenYearCHD, pred.logit)

# Sensitivity and Specificity for each model
lin.sen <- roc_lin$sensitivities
lin.spe <- roc_lin$specificities
rad.sen <- roc_RBF$sensitivities
rad.spe <- roc_RBF$specificities
logit.sen <- roc_logit$sensitivities
logit.spe <- roc_logit$specificities

# AUC values
auc.lin <- roc_lin$auc
auc.rad <- roc_RBF$auc
auc.logit <- roc_logit$auc

# Plot ROC curves
plot(1 - lin.spe, lin.sen,  
     xlab = "1 - Specificity",
     ylab = "Sensitivity",
     col = "darkred",
     type = "l",
     lty = 1,
     lwd = 1,
     main = "ROC Curves of SVM and Logistic Regression")
lines(1 - rad.spe, rad.sen, 
      col = "blue",
      lty = 1,
      lwd = 1)
lines(1 - logit.spe, logit.sen,      
      col = "orange",
      lty = 1,
      lwd = 1)

# Add the diagonal line for random guessing
abline(0, 1, col = "skyblue3", lty = 2, lwd = 2)

# Add vertical lines for thresholds
abline(v = c(0.049, 0.151), lty = 3, col = "darkgreen")

# Legend for the plot
legend("bottomright", c("Linear SVM", "Radial SVM", "Logistic Regression"),
       lty = c(1, 1, 1), lwd = rep(1, 3),
       col = c("red", "blue", "orange"),
       bty = "n", cex = 0.8)

# Annotate with AUC values
text(0.8, 0.46, paste("Linear AUC: ", round(auc.lin, 4)), cex = 0.8)
text(0.8, 0.4, paste("Radial AUC: ", round(auc.rad, 4)), cex = 0.8)
text(0.8, 0.34, paste("Logistic AUC: ", round(auc.logit, 4)), cex = 0.8)

The ROC curve above indicates that the linear SVM, RBF SVM, and standard linear logistic regression models do not perform equally well on a global scale. However, one notable observation from the ROC curve is that the sensitivity of both the Radial and logistic regression models is consistently higher than that of the linear SVM when the specificity level is between 85% and 95%. For the question of classification, the best model to use is the logistic regression

Conclusion

Overall in this project we have done some EDA with linear/logistical regularized regression, as well as utilizing SVMs to predict glucose levels and label if someone will have CHD in ten years. We have cross-validated these different models to see which one performed the best in order to answer these key questions.

LS0tDQp0aXRsZTogJ0V4cGxvcmluZyBDcmVkaXQgUmlzayBmb3IgTGVuZGVycyBhbmQgQm9ycm93ZXJzJw0KYXV0aG9yOiAiIEplc3NpY2EgR29yciINCmRhdGU6ICI0LzIvMjAyNSINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgICB0b2NfZmxvYXQ6IHllcw0KICAgIG51bWJlcl9zZWN0aW9uczogbm8NCiAgICB0b2NfY29sbGFwc2VkOiB5ZXMNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCiAgICBzbW9vdGhfc2Nyb2xsOiB5ZXMNCiAgICB0aGVtZTogbHVtZW4NCiAgd29yZF9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgICBmaWdfY2FwdGlvbjogeWVzDQogICAga2VlcF9tZDogeWVzDQogIHBkZl9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgICBmaWdfY2FwdGlvbjogeWVzDQogICAgbnVtYmVyX3NlY3Rpb25zOiBubw0KICAgIGZpZ193aWR0aDogMw0KICAgIGZpZ19oZWlnaHQ6IDMNCmVkaXRvcl9vcHRpb25zOiANCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGlubGluZQ0KLS0tDQoNCmBgYHs9aHRtbH0NCg0KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4NCg0KLyogQ2FzY2FkaW5nIFN0eWxlIFNoZWV0cyAoQ1NTKSBpcyBhIHN0eWxlc2hlZXQgbGFuZ3VhZ2UgdXNlZCB0byBkZXNjcmliZSB0aGUgcHJlc2VudGF0aW9uIG9mIGEgZG9jdW1lbnQgd3JpdHRlbiBpbiBIVE1MIG9yIFhNTC4gaXQgaXMgYSBzaW1wbGUgbWVjaGFuaXNtIGZvciBhZGRpbmcgc3R5bGUgKGUuZy4sIGZvbnRzLCBjb2xvcnMsIHNwYWNpbmcpIHRvIFdlYiBkb2N1bWVudHMuICovDQoNCmgxLnRpdGxlIHsgIC8qIFRpdGxlIC0gZm9udCBzcGVjaWZpY2F0aW9ucyBvZiB0aGUgcmVwb3J0IHRpdGxlICovDQogIGZvbnQtc2l6ZTogMjJweDsNCiAgZm9udC13ZWlnaHQ6IGJvbGQ7DQogIGNvbG9yOiBEYXJrUmVkOw0KICB0ZXh0LWFsaWduOiBjZW50ZXI7DQogIGZvbnQtZmFtaWx5OiAiR2lsbCBTYW5zIiwgc2Fucy1zZXJpZjsNCn0NCmg0LmF1dGhvciB7IC8qIEhlYWRlciA0IC0gZm9udCBzcGVjaWZpY2F0aW9ucyBmb3IgYXV0aG9ycyAgKi8NCiAgZm9udC1zaXplOiAxOHB4Ow0KICBmb250LXdlaWdodDogYm9sZDsNCiAgZm9udC1mYW1pbHk6IHN5c3RlbS11aTsNCiAgY29sb3I6IG5hdnk7DQogIHRleHQtYWxpZ246IGNlbnRlcjsNCn0NCmg0LmRhdGUgeyAvKiBIZWFkZXIgNCAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgZm9yIHRoZSBkYXRlICAqLw0KICBmb250LXNpemU6IDE4cHg7DQogIGZvbnQtZmFtaWx5OiBzeXN0ZW0tdWk7DQogIGNvbG9yOiBEYXJrQmx1ZTsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KICBmb250LXdlaWdodDogYm9sZDsNCn0NCmgxIHsgLyogSGVhZGVyIDEgLSBmb250IHNwZWNpZmljYXRpb25zIGZvciBsZXZlbCAxIHNlY3Rpb24gdGl0bGUgICovDQogICAgZm9udC1zaXplOiAxOHB4Ow0KICAgIGZvbnQtZmFtaWx5OiAiR2lsbCBTYW5zIiwgc2Fucy1zZXJpZjsNCiAgICBjb2xvcjogbmF2eTsNCiAgICB0ZXh0LWFsaWduOiBjZW50ZXI7DQogICAgZm9udC13ZWlnaHQ6IGJvbGQ7DQp9DQpoMiB7IC8qIEhlYWRlciAyIC0gZm9udCBzcGVjaWZpY2F0aW9ucyBmb3IgbGV2ZWwgMiBzZWN0aW9uIHRpdGxlICovDQogICAgZm9udC1zaXplOiAxNnB4Ow0KICAgIGZvbnQtZmFtaWx5OiAiR2lsbCBTYW5zIiwgc2Fucy1zZXJpZjsNCiAgICBjb2xvcjogbmF2eTsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KICAgIGZvbnQtd2VpZ2h0OiBib2xkOw0KfQ0KDQpoMyB7IC8qIEhlYWRlciAzIC0gZm9udCBzcGVjaWZpY2F0aW9ucyBvZiBsZXZlbCAzIHNlY3Rpb24gdGl0bGUgICovDQogICAgZm9udC1zaXplOiAxNHB4Ow0KICAgIGZvbnQtZmFtaWx5OiAiR2lsbCBTYW5zIiwgc2Fucy1zZXJpZjsNCiAgICBjb2xvcjogbmF2eTsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KDQpoNCB7IC8qIEhlYWRlciA0IC0gZm9udCBzcGVjaWZpY2F0aW9ucyBvZiBsZXZlbCA0IHNlY3Rpb24gdGl0bGUgICovDQogICAgZm9udC1zaXplOiAxMnB4Ow0KICAgIGZvbnQtZmFtaWx5OiAiR2lsbCBTYW5zIiwgc2Fucy1zZXJpZjsNCiAgICBjb2xvcjogZGFya3JlZDsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KDQpib2R5IHsgYmFja2dyb3VuZC1jb2xvcjp3aGl0ZTsgfQ0KDQouaGlnaGxpZ2h0bWUgeyBiYWNrZ3JvdW5kLWNvbG9yOnllbGxvdzsgfQ0KDQpwIHsgYmFja2dyb3VuZC1jb2xvcjp3aGl0ZTsgfQ0KDQo8L3N0eWxlPg0KYGBgDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0KIyBjb2RlIGNodW5rIHNwZWNpZmllcyB3aGV0aGVyIHRoZSBSIGNvZGUsIHdhcm5pbmdzLCBhbmQgb3V0cHV0DQojIHdpbGwgYmUgaW5jbHVkZWQgaW4gdGhlIG91dHB1dCBmaWxlcy4NCmlmICghcmVxdWlyZSgia25pdHIiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygia25pdHIiKQ0KICAgbGlicmFyeShrbml0cikNCn0NCg0KaWYgKCFyZXF1aXJlKCJkcGx5ciIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJkcGx5ciIpDQogICBsaWJyYXJ5KGRwbHlyKQ0KfQ0KDQppZiAoIXJlcXVpcmUoImNhcmV0IikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoImNhcmV0IikNCiAgIGxpYnJhcnkoY2FyZXQpDQp9DQppZiAoIXJlcXVpcmUoImdncGxvdDIiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygiZ2dwbG90MiIpDQogICBsaWJyYXJ5KGdncGxvdDIpDQp9DQogIA0KaWYgKCFyZXF1aXJlKCJwYXRjaHdvcmsiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygicGF0Y2h3b3JrIikNCiAgIGxpYnJhcnkocGF0Y2h3b3JrKQ0KfQ0KDQppZiAoIXJlcXVpcmUoInJlc2hhcGUyIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoInJlc2hhcGUyIikNCiAgIGxpYnJhcnkocmVzaGFwZTIpDQp9DQoNCmlmICghcmVxdWlyZSgidGlueXRleCIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJ0aW55dGV4IikNCiAgIGxpYnJhcnkodGlueXRleCkNCn0NCg0KDQppZiAoIXJlcXVpcmUoIm5ldXJhbG5ldCIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJuZXVyYWxuZXQiKQ0KICAgbGlicmFyeShuZXVyYWxuZXQpDQp9DQoNCmlmICghcmVxdWlyZSgicnBhcnQiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygicnBhcnQiKQ0KICAgbGlicmFyeShycGFydCkNCn0NCg0KaWYgKCFyZXF1aXJlKCJycGFydC5wbG90IikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoInJwYXJ0LnBsb3QiKQ0KICAgbGlicmFyeShycGFydC5wbG90KQ0KfQ0KDQppZiAoIXJlcXVpcmUoInBST0MiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygicFJPQyIpDQogICBsaWJyYXJ5KHBST0MpDQp9DQoNCmlmICghcmVxdWlyZSgidmlwIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoInZpcCIpDQogICBsaWJyYXJ5KHZpcCkNCn0NCg0KaWYgKCFyZXF1aXJlKCJnZ2ZvcnRpZnkiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygiZ2dmb3J0aWZ5IikNCiAgIGxpYnJhcnkodmlwKQ0KfQ0KDQppZiAoIXJlcXVpcmUoImRic2NhbiIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJkYnNjYW4iKQ0KICAgbGlicmFyeShkYnNjYW4pDQp9DQppZiAoIXJlcXVpcmUoInZjZCIpKXsNCiAgaW5zdGFsbC5wYWNrYWdlcygidmNkIikNCiAgbGlicmFyeSh2Y2QpDQp9DQoNCmlmICghcmVxdWlyZSgiY2x1c3RlciIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJjbHVzdGVyIikNCiAgIGxpYnJhcnkoY2x1c3RlcikNCn0NCg0KaWYgKCFyZXF1aXJlKCJmYWN0b2V4dHJhIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoImZhY3RvZXh0cmEiKQ0KICAgbGlicmFyeShmYWN0b2V4dHJhKQ0KfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCAjIGluY2x1ZGUgY29kZSBjaHVuayBpbiB0aGUNCiAjIG91dHB1dCBmaWxlDQogd2FybmluZ3NzID0gRkFMU0UsICMgc29tZXRpbWVzLCB5b3UgY29kZSBtYXkNCiAjIHByb2R1Y2Ugd2FybmluZ3MgbWVzc2FnZXMsDQojIHlvdSBjYW4gY2hvb3NlIHRvIGluY2x1ZGUNCiMgdGhlIHdhcm5pbmdzIG1lc3NhZ2VzIGluDQogIyB0aGUgb3V0cHV0IGZpbGUuDQogcmVzdWx0cyA9IFRSVUUsICMgeW91IGNhbiBhbHNvIGRlY2lkZSB3aGV0aGVyDQogIyB0byBpbmNsdWRlIHRoZSBvdXRwdXQNCiMgaW4gdGhlIG91dHB1dCBmaWxlLg0KIG1lc3NhZ2UgPSBGQUxTRSwNCiBjb21tZW50ID0gTkENCikgIA0KYGBgDQoNCg0KDQojIyAxLiBJbnRyb2R1Y3Rpb24NCg0KVGhlIEZyYW1pbmdoYW0gSGVhcnQgU3R1ZHkgaXMgYSBsb25nLXRlcm0gcHJvc3BlY3RpdmUgc3R1ZHkgb2YgdGhlIGV0aW9sb2d5IG9mIGNhcmRpb3Zhc2N1bGFyIGRpc2Vhc2UgYW1vbmcgYSBwb3B1bGF0aW9uIG9mIGZyZWUtbGl2aW5nIHN1YmplY3RzIGluIHRoZSBjb21tdW5pdHkgb2YgRnJhbWluZ2hhbSwgTWFzc2FjaHVzZXR0cy4gVGhlIEZyYW1pbmdoYW0gSGVhcnQgU3R1ZHkgd2FzIGEgbGFuZG1hcmsgc3R1ZHkgaW4gZXBpZGVtaW9sb2d5IGluIHRoYXQgaXQgd2FzIHRoZSBmaXJzdCBwcm9zcGVjdGl2ZSBzdHVkeSBvZiBjYXJkaW92YXNjdWxhciBkaXNlYXNlIGFuZCBpZGVudGlmaWVkIHRoZSBjb25jZXB0IG9mIHJpc2sgZmFjdG9ycyBhbmQgdGhlaXIgam9pbnQgZWZmZWN0cyBGSFMgTG9uZ2l0dWRpbmFsIERhdGEgRG9jdW1lbnQuDQoNCg0KVGhlIGRhdGFzZXQgaXMgY29tcG9zZWQgYnkgNDI0MCByb3dzIChvYnNlcnZhdGlvbnMpIGFuZCAxNiBjb2x1bW5zICh2YXJpYWJsZXMpLCB3aGljaCBhcmUgZGVzY3JpYmVkIGJlbG93Og0KICANCg0KMS4gc2V4OiB0aGUgZ2VuZGVyIG9mIHRoZSBvYnNlcnZhdGlvbnMuIFRoZSB2YXJpYWJsZSBpcyBhIGJpbmFyeSBuYW1lZCDigJxtYWxl4oCdIGluIHRoZSBkYXRhc2V0Lg0KMi4gYWdlOiBBZ2UgYXQgdGhlIHRpbWUgb2YgbWVkaWNhbCBleGFtaW5hdGlvbiBpbiB5ZWFycy4NCjMuIGVkdWNhdGlvbjogQSBjYXRlZ29yaWNhbCB2YXJpYWJsZSBvZiB0aGUgcGFydGljaXBhbnRzIGVkdWNhdGlvbiwgd2l0aCB0aGUgbGV2ZWxzOiBTb21lIGhpZ2ggc2Nob29sICgxKSwgaGlnaCBzY2hvb2wvR0VEICgyKSwgc29tZSBjb2xsZWdlL3ZvY2F0aW9uYWwgc2Nob29sICgzKSwgY29sbGVnZSAoNCkNCjQuIGN1cnJlbnRTbW9rZXI6IEN1cnJlbnQgY2lnYXJldHRlIHNtb2tpbmcgYXQgdGhlIHRpbWUgb2YgZXhhbWluYXRpb25zDQo1LiBjaWdzUGVyRGF5OiBOdW1iZXIgb2YgY2lnYXJldHRlcyBzbW9rZWQgZWFjaCBkYXkNCjYuIEJQbWVkczogVXNlIG9mIEFudGktaHlwZXJ0ZW5zaXZlIG1lZGljYXRpb24gYXQgZXhhbQ0KNy4gcHJldmFsZW50U3Ryb2tlOiBQcmV2YWxlbnQgU3Ryb2tlICgwID0gZnJlZSBvZiBkaXNlYXNlKQ0KOC4gcHJldmFsZW50SHlwOiBQcmV2YWxlbnQgSHlwZXJ0ZW5zaXZlLiBTdWJqZWN0IHdhcyBkZWZpbmVkIGFzIGh5cGVydGVuc2l2ZSBpZiB0cmVhdGVkDQo5LiBkaWFiZXRlczogRGlhYmV0aWMgYWNjb3JkaW5nIHRvIGNyaXRlcmlhIG9mIGZpcnN0IGV4YW0gdHJlYXRlZA0KMTAuIHRvdENob2w6IFRvdGFsIGNob2xlc3Rlcm9sIChtZy9kTCkNCjExLiBzeXNCUDogU3lzdG9saWMgQmxvb2QgUHJlc3N1cmUgKG1tSGcpDQoxMi4gZGlhQlA6IERpYXN0b2xpYyBibG9vZCBwcmVzc3VyZSAobW1IZykNCjEzLiBCTUk6IEJvZHkgTWFzcyBJbmRleCwgd2VpZ2h0IChrZykvaGVpZ2h0IChtKV4yDQoxNC4gaGVhcnRSYXRlOiBIZWFydCByYXRlIChiZWF0cy9taW51dGUpDQoxNS4gZ2x1Y29zZTogQmxvb2QgZ2x1Y29zZSBsZXZlbCAobWcvZEwpDQoxNi4gVGVuWWVhckNIRChyZXNwb25zZSB2YXJpYWJsZSk6IFRoZSAxMCB5ZWFyIHJpc2sgb2YgY29yb25hcnkgaGVhcnQgZGlzZWFzZShDSEQpLg0KDQoNCkluIHRoaXMgYW5hbHlzaXMsIHdlIHdpbGwgYXBwbHkgdGhyZWUgcmVndWxhcml6ZWQgcmVncmVzc2lvbiB0ZWNobmlxdWVz4oCUTGFzc28sIFJpZGdlLCBhbmQgRWxhc3RpYyBOZXTigJR0byBwcmVkaWN0IGdsdWNvc2UgbGV2ZWxzIGJhc2VkIG9uIHZhcmlvdXMgaGVhbHRoLXJlbGF0ZWQgZmVhdHVyZXMuIFdlIHdpbGwgYWxzbyB1c2UgdXNlIHJlZ3VsYXJpemVkIGxvZ2lzdGljIHJlZ3Jlc3Npb24gdG8gcHJlZGljdCB0aGUgdGVuIHllYXIgcmlzayBvZiBvZiBjb3JvbmFyeSBoZWFydCBkZWNlYXNlLiBGaW5hbGx5IHdlIHdpbGwgYnVpbGQgU1ZNcyB0byBleHBsb3JlIHRoZSBzYW1lIHF1ZXN0aW9ucy4gVGhlc2UgbWV0aG9kcyBhcmUgdXNlZCB0byBhZGRyZXNzIHBvdGVudGlhbCBvdmVyZml0dGluZyBpbiBwcmVkaWN0aXZlIG1vZGVscywgZXNwZWNpYWxseSB3aGVuIHRoZXJlIGFyZSBtYW55IHByZWRpY3RvciB2YXJpYWJsZXMgb3IgbXVsdGljb2xsaW5lYXJpdHkgYW1vbmcgdGhlbS4gV2Ugd2lsbCBwZXJmb3JtIGV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMgdG8gZmluZCBzb21lIGtleSBmaW5kaW5ncyBhbmQgcXVlc3Rpb25zIHRoYXQgbGllIHdpdGggdGhlIGRhdGFzZXQuIFRoZW4gaW1wdXRpbmcgbWlzc2luZyB2YXJpYWJsZXMgdXNpbmcgdGhlIE1JQ0UgbWV0aG9kIHdpbGwgYmUgdXNlZCBmb3IgZmVhdHVyaW5nIGVuZ2luZWVyaW5nIGFuZCBzZWxlY3RpbmcgdGhlIGZlYXR1cmVzIHRvIGFuc3dlciB0aGUgdHdvIHF1ZXN0aW9ucyBmb3IgbGluZWFyIGFuZCBsb2dpc3RpY2FsIG1vZGVsaW5nLiANCg0KDQpgYGB7ciwgd2FybmluZ3M9RkFMU0V9DQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShyZXNoYXBlMikNCmxpYnJhcnkodGlkeXIpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHRpYmJsZSkNCg0KI0xvYWQgdGhlIHNhbXBsZSBkYXRhDQpoZWFydHMgPSByZWFkLmNzdigiaHR0cHM6Ly9qZXNzZ29ycjAxLmdpdGh1Yi5pby9TVEE1NTEvc3RhNTUyL3Byb2plY3QyL0ZyYW1pbmdoYW1IZWFydFN0dWR5LmNzdiIpDQoNCg0KaGVhZChoZWFydHMpDQpgYGANCg0KIyMgMi4gRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpcw0KDQpUaGUgZmlyc3Qgc3RlcCBpbiB0aGUgYW5hbHlzaXMgaXMgbG9va2luZyBhdCBhbGwgdGhlIGluZGl2aWR1YWwgZmVhdHVyZXMuIEluIG9yZGVyIHRvIHBlcmZvcm0gdGhpcyBhY3Rpb24sIGFsbCBvZiB0aGUgdmFyaWFibGVzIHRoZSB0aGUgZGF0YXNldCB3aWxsIGJlIGNvbnZlcnRlZCB0byBmYWN0b3JzLiB0aGlzIGNhbiBiZSBzZWVuIGluIHRoZSBjb2RlIGNodW5rIGJlbG93LiBGb2xsb3dpbmcgdGhpcywgYWxsIG1pc3NpbmcgZGF0YSB3aWxsIGJlIGlkZW50aWZpZWQgYW5kIGEgc3VtbWFyeSB3aWxsIGJlIGdpdmVuIHRvIGdldCBhIGdlbmVyYWwgb3ZlcnZpZXcgb2YgdGhlIGNyZWRpdCByaXNrIGRhdGEuIFRoZW4gdmlzdWFscyB3aWxsIGJlIGdpdmVuIHRvIGxvb2tpbmcgYXQgdGhlIGRpc3RyaWJ1dGlvbnMgb2YgaW5kaXZpZHVhbCBmYWN0b3JzLCBhbmQgd2hhdCBpbnNpZ2h0cyBjYW4gYmUgZGlzY292ZXJlZCBmb3IgZnV0dXJlIHByZWRpY3RpbmcgYW5kIGFuYWx5c2lzLg0KDQoNCiMjIyBDbGVhbmluZyBhbmQgZmlyc3QgbG9va3MNCg0KQmVsb3cgaXMgYSBjaHVuayB0byB0dXJuIGFsbCB0aGUgdmFyaWFibGVzIGludG8gZmFjdG9ycy4NCmBgYHtyLCB3YXJuaW5ncz1GQUxTRX0NCg0KbGlicmFyeShkcGx5cikNCg0KI2NvbG5hbWVzKGhlYXJ0cykNCg0KIyBDb252ZXJ0IGFsbCBjb2x1bW5zIGludG8gZmFjdG9ycw0KaGVhcnRzX2ZhY3Rvcml6ZWQgPC0gaGVhcnRzICU+JQ0KICBtdXRhdGUoDQogICAgbWFsZSA9IGZhY3RvcihtYWxlKSwgICAgICAgICAgICAgICAgICAgICAgICAjIENvbnZlcnQgZ2VuZGVyIHRvIGZhY3RvciAoYmluYXJ5KQ0KICAgIGFnZSA9IGZhY3RvcihhZ2UpLCAgICAgICAgICAgICAgICAgICAgICAgICMgQ29udmVydCBhZ2UgdG8gZmFjdG9yICh0aG91Z2ggaXTigJlzIGNvbnRpbnVvdXMsIHdlIGNhbiB0cmVhdCBpdCBhcyBhIGNhdGVnb3J5IGlmIG5lZWRlZCkNCiAgICBlZHVjYXRpb24gPSBmYWN0b3IoZWR1Y2F0aW9uLCBsZXZlbHMgPSAxOjQsIGxhYmVscyA9IGMoIlNvbWUgaGlnaCBzY2hvb2wiLCAiSGlnaCBzY2hvb2wvR0VEIiwgIlNvbWUgY29sbGVnZS92b2NhdGlvbmFsIHNjaG9vbCIsICJDb2xsZWdlIikpLA0KICAgIGN1cnJlbnRTbW9rZXIgPSBmYWN0b3IoY3VycmVudFNtb2tlciwgbGV2ZWxzID0gYygwLCAxKSwgbGFiZWxzID0gYygiTm8iLCAiWWVzIikpLA0KICAgIGNpZ3NQZXJEYXkgPSBmYWN0b3IoY2lnc1BlckRheSksICAgICAgICAgICMgQ29udmVydCBudW1iZXIgb2YgY2lnYXJldHRlcyBwZXIgZGF5IHRvIGEgZmFjdG9yDQogICAgQlBNZWRzID0gZmFjdG9yKEJQTWVkcywgbGV2ZWxzID0gYygwLCAxKSwgbGFiZWxzID0gYygiTm8iLCAiWWVzIikpLA0KICAgIHByZXZhbGVudFN0cm9rZSA9IGZhY3RvcihwcmV2YWxlbnRTdHJva2UsIGxldmVscyA9IGMoMCwgMSksIGxhYmVscyA9IGMoIk5vIiwgIlllcyIpKSwNCiAgICBwcmV2YWxlbnRIeXAgPSBmYWN0b3IocHJldmFsZW50SHlwLCBsZXZlbHMgPSBjKDAsIDEpLCBsYWJlbHMgPSBjKCJObyIsICJZZXMiKSksDQogICAgZGlhYmV0ZXMgPSBmYWN0b3IoZGlhYmV0ZXMsIGxldmVscyA9IGMoMCwgMSksIGxhYmVscyA9IGMoIk5vIiwgIlllcyIpKSwNCiAgICB0b3RDaG9sID0gZmFjdG9yKHRvdENob2wpLCAgICAgICAgICAgICAgICAjIENvbnZlcnQgY2hvbGVzdGVyb2wgdG8gZmFjdG9yDQogICAgc3lzQlAgPSBmYWN0b3Ioc3lzQlApLCAgICAgICAgICAgICAgICAgICAgIyBDb252ZXJ0IHN5c3RvbGljIGJsb29kIHByZXNzdXJlIHRvIGZhY3Rvcg0KICAgIGRpYUJQID0gZmFjdG9yKGRpYUJQKSwgICAgICAgICAgICAgICAgICAgICMgQ29udmVydCBkaWFzdG9saWMgYmxvb2QgcHJlc3N1cmUgdG8gZmFjdG9yDQogICAgQk1JID0gZmFjdG9yKEJNSSksICAgICAgICAgICAgICAgICAgICAgICAgIyBDb252ZXJ0IEJNSSB0byBmYWN0b3INCiAgICBoZWFydFJhdGUgPSBmYWN0b3IoaGVhcnRSYXRlKSwgICAgICAgICAgICAjIENvbnZlcnQgaGVhcnQgcmF0ZSB0byBmYWN0b3INCiAgICBnbHVjb3NlID0gZmFjdG9yKGdsdWNvc2UpLCAgICAgICAgICAgICAgICAjIENvbnZlcnQgZ2x1Y29zZSB0byBmYWN0b3INCiAgICBUZW5ZZWFyQ0hEID0gZmFjdG9yKFRlblllYXJDSEQpICAgICAgICAgICAjIENvbnZlcnQgcmVzcG9uc2UgdmFyaWFibGUgdG8gZmFjdG9yIChpZiBpdCdzIGJpbmFyeSkNCiAgKQ0KDQojIENoZWNrIHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIG1vZGlmaWVkIGRhdGFzZXQNCiNzdHIoaGVhcnRzX2ZhY3Rvcml6ZWQpDQoNCiMgQ2hlY2sgdGhlIGZpcnN0IGZldyByb3dzIHRvIGNvbmZpcm0gY2hhbmdlcw0KaGVhZChoZWFydHNfZmFjdG9yaXplZCkNCg0KYGBgDQpUaGUgc3VtbWFyeSBvZiBvdXIgZGF0YSwgaW5jbHVkaW5nIG1pc3NpbmcgdmFsdWVzIGlzIGJlbG93Og0KYGBge3IsIHdhcm5pbmdzPUZBTFNFfQ0Kc3VtbWFyeShoZWFydHNfZmFjdG9yaXplZCkNCmBgYA0KRnJvbSB0aGUgc3VtbWFyeSwgdGhlcmUgYXJlIG11bHRpcGxlIG1pc3Npbmcgb2JzZXJ2YXRpb25zLCBzbyB3ZSBtdXN0IHVzZSBtdWx0aXBsZSBpbXB1dGF0aW9uIGluIG9yZGVyIHRvIGZpbGwgdGhlIGdhcHMuDQoNCiMjIyBNdWx0aWJsZSBJbXB1dGF0aW9uDQoNCkluIG9yZGVyIHRvIGltcHV0ZSBhbGwgdGhlIG1pc3NpbmcgdmFsdWVzIGluIHRoZSBkYXRhLCBtdWx0aXBsZSByZWdyZXNzaW9uIGltcHV0YXRpb24gd2lsbCBiZSBkb25lIHRocm91Z2ggdGhlIE1JQ0UgbGlicmFyeS4gTXVsdGlwbGUgUmVncmVzc2lvbi1iYXNlZCBpbXB1dGF0aW9uIGlzIGEgbWV0aG9kIHdoZXJlIG1pc3NpbmcgbnVtZXJpY2FsIHZhbHVlcyBhcmUgcHJlZGljdGVkIHVzaW5nIHJlZ3Jlc3Npb24gbW9kZWxzIGJhc2VkIG9uIG90aGVyIGF2YWlsYWJsZSBmZWF0dXJlcy4gVGhpcyBpbXB1dGF0aW9uIG1ldGhvZCBoZWxwcyBtYWludGFpbiByZWxhdGlvbnNoaXBzIGJldHdlZW4gdmFyaWFibGVzIGFuZCBwcm92aWRlcyBtb3JlIGFjY3VyYXRlIGltcHV0YXRpb25zIGNvbXBhcmVkIHRvIG1vZGUgaW1wdXRhdGlvbiBmb3IgY2F0ZWdvcmljYWwgdmFyaWFibGVzIGFuZCBtZWRpYW4vbWVhbiBmb3IgbnVtZXJpY2FsIHZhcmlhYmxlcy4NCg0KDQpgYGB7ciwgd2FybmluZ3M9RkFMU0V9DQpsaWJyYXJ5KG1pY2UpDQoNCmhlYXJ0c19jbGVhbiA8LSBoZWFydHNfZmFjdG9yaXplZCAlPiUgZHJvcF9uYSgpDQpzdW1tYXJ5KGhlYXJ0c19jbGVhbikNCmBgYA0KDQpOb3cgdGhhdCB3ZSBoYXZlIGNsZWFuZWQgZGF0YSwgd2UgY2FuIG1vdmUgb250byBsb29raW5nIGF0IHRoZSBkaXN0cmlidXRpb25zIGFuZCBmZWF0dXJlIGVuZ2luZWVyaW5nLg0KDQoNCg0KIyMjIERpc3RyaWJ1dGlvbnMNCg0KRm9yIHRoaXMgYW5hbHlzaXMsIGV4YW1pbmluZyB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSB2YXJpYWJsZXMgaXMgY3J1Y2lhbCB0byBlbnN1cmUgdGhlIHJlbGlhYmlsaXR5IGFuZCBhY2N1cmFjeSBvZiB0aGUgbW9kZWxpbmcgcmVzdWx0cy4gVG8gc3RhcnQgd2Ugd2lsbCBsb29rIGF0IHRoZSBudW1lcmljYWwgdmFyaWFibGVzLiBUaGUgY29udGludW91cyB2YXJpYWJsZXMgc3VjaCBhcyBhZ2UsIGNob2xlc3Rlcm9sIGxldmVscywgYmxvb2QgcHJlc3N1cmUsIEJNSSwgYW5kIGdsdWNvc2UgbGV2ZWxzIG5lZWQgdG8gYmUgYXNzZXNzZWQgZm9yIG5vcm1hbGl0eSwgYXMgZGV2aWF0aW9ucyBmcm9tIG5vcm1hbCBkaXN0cmlidXRpb24gbWlnaHQgc3VnZ2VzdCB0aGUgbmVlZCBmb3IgZGF0YSB0cmFuc2Zvcm1hdGlvbiBvciBub24tcGFyYW1ldHJpYyBtZXRob2RzLg0KDQpWaXN1YWxpemF0aW9ucyBzdWNoIGFzIGhpc3RvZ3JhbXMgZm9yIGNvbnRpbnVvdXMgdmFyaWFibGVzIGFuZCBiYXIgcGxvdHMgZm9yIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyB3aWxsIGhlbHAgdXMgaWRlbnRpZnkgYW55IHVudXN1YWwgcGF0dGVybnMgb3Igc2tld2VkIGRpc3RyaWJ1dGlvbnMsIGVuYWJsaW5nIHVzIHRvIGFwcGx5IHRoZSBuZWNlc3NhcnkgcHJlcHJvY2Vzc2luZyBzdGVwcyBiZWZvcmUgZml0dGluZyBtb2RlbHMgbGlrZSBMYXNzbywgUmlkZ2UsIG9yIEVsYXN0aWMgTmV0IHJlZ3Jlc3Npb24uIFRoaXMgZW5zdXJlcyB0aGF0IHRoZSBhbmFseXNpcyBpcyByb2J1c3QgYW5kIHRoZSByZXN1bHRzIGFyZSBpbnRlcnByZXRhYmxlIGFuZCByZWxpYWJsZS4NCmBgYHtyLCB3YXJuaW5ncz1GQUxTRX0NCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkocGxvdGx5KQ0KDQoNCiMgU2VsZWN0IG9ubHkgbnVtZXJpY2FsIHZhcmlhYmxlcw0KbnVtZXJpY2FsX3ZhcnMgPC0gaGVhcnRzWywgc2FwcGx5KGhlYXJ0cywgaXMubnVtZXJpYyldDQoNCiMgQ3JlYXRlIGEgbGlzdCB0byBzdG9yZSBnZ3Bsb3Qgb2JqZWN0cyBmb3IgZWFjaCB2YXJpYWJsZQ0KcGxvdF9saXN0IDwtIGxpc3QoKQ0KDQojIENyZWF0ZSBhIGhpc3RvZ3JhbSBmb3IgZWFjaCBudW1lcmljYWwgdmFyaWFibGUgYW5kIHN0b3JlIHRoZSBwbG90cyBpbiBwbG90X2xpc3QNCmZvciAodmFyIGluIG5hbWVzKG51bWVyaWNhbF92YXJzKSkgew0KICAjIENoZWNrIGlmIHRoZSB2YXJpYWJsZSBpcyBudW1lcmljIGJlZm9yZSBwbG90dGluZw0KICBpZiAoaXMubnVtZXJpYyhoZWFydHNfY2xlYW5bW3Zhcl1dKSkgew0KICAgIHAgPC0gZ2dwbG90KGhlYXJ0c19jbGVhbiwgYWVzX3N0cmluZyh4ID0gdmFyKSkgKw0KICAgICAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxMCwgZmlsbCA9ICJza3libHVlIiwgY29sb3IgPSAiYmxhY2siLCBhbHBoYSA9IDAuNykgKw0KICAgICAgdGhlbWVfbWluaW1hbCgpICsNCiAgICAgIGxhYnModGl0bGUgPSBwYXN0ZSgiRGlzdHJpYnV0aW9uIG9mIiwgdmFyKSwgeCA9IHZhciwgeSA9ICJGcmVxdWVuY3kiKSArDQogICAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KICB9IGVsc2Ugew0KICAgICMgSWYgbm90IG51bWVyaWMsIHVzZSBnZW9tX2JhciAob3Igb3RoZXIgYXBwcm9wcmlhdGUgcGxvdHMpDQogICAgcCA8LSBnZ3Bsb3QoaGVhcnRzX2NsZWFuLCBhZXNfc3RyaW5nKHggPSB2YXIpKSArDQogICAgICBnZW9tX2JhcihmaWxsID0gInNreWJsdWUiLCBjb2xvciA9ICJibGFjayIsIGFscGhhID0gMC43KSArDQogICAgICB0aGVtZV9taW5pbWFsKCkgKw0KICAgICAgbGFicyh0aXRsZSA9IHBhc3RlKCJEaXN0cmlidXRpb24gb2YiLCB2YXIpLCB4ID0gdmFyLCB5ID0gIkZyZXF1ZW5jeSIpICsNCiAgICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpDQogIH0NCiAgDQogICMgQ29udmVydCBnZ3Bsb3QgdG8gcGxvdGx5IGludGVyYWN0aXZlIHBsb3QNCiAgcGxvdF9saXN0W1t2YXJdXSA8LSBnZ3Bsb3RseShwKQ0KfQ0KDQojIENyZWF0ZSBpbmRpdmlkdWFsIGludGVyYWN0aXZlIHBhbmVscyBmb3IgZWFjaCBwbG90DQpmb3IgKHAgaW4gcGxvdF9saXN0KSB7DQogIHByaW50KHApDQp9DQpgYGANCg0KKEYuLi4uLi4uLi4uLi4uLi4uLi4uLi4uKQ0KDQpPbiB0aGUgb3RoZXIgaGFuZCwgY2F0ZWdvcmljYWwgdmFyaWFibGVzIGxpa2UgZ2VuZGVyLCBzbW9raW5nIHN0YXR1cywgYW5kIGVkdWNhdGlvbiBsZXZlbCBzaG91bGQgYmUgY2hlY2tlZCBmb3IgY2xhc3MgaW1iYWxhbmNlcywgYXMgc2lnbmlmaWNhbnQgaW1iYWxhbmNlcyBjb3VsZCBsZWFkIHRvIGJpYXNlZCBtb2RlbCBwcmVkaWN0aW9ucy4gV2Ugd2lsbCBsb29rIGF0IHRoZSBjYXRlZ29yaWNhbCB2YXJpYmxlcyBiZWx3by4NCg0KICANCmBgYHtyLCB3YXJuaW5ncz1GQUxTRX0NCiMgTG9hZCBuZWNlc3NhcnkgbGlicmFyaWVzDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHBsb3RseSkNCg0KDQojIFNlbGVjdCBvbmx5IGNhdGVnb3JpY2FsIHZhcmlhYmxlcyAoZmFjdG9ycyBvciBjaGFyYWN0ZXJzKQ0KY2F0ZWdvcmljYWxfdmFycyA8LSBoZWFydHNbLCBzYXBwbHkoaGVhcnRzLCBpcy5mYWN0b3IpIHwgc2FwcGx5KGhlYXJ0cywgaXMuY2hhcmFjdGVyKV0NCg0KIyBDcmVhdGUgYSBsaXN0IHRvIHN0b3JlIGdncGxvdCBvYmplY3RzIGZvciBlYWNoIGNhdGVnb3JpY2FsIHZhcmlhYmxlDQpwbG90X2xpc3RfY2F0IDwtIGxpc3QoKQ0KDQojIENyZWF0ZSBhIGJhciBwbG90IGZvciBlYWNoIGNhdGVnb3JpY2FsIHZhcmlhYmxlIGFuZCBzdG9yZSB0aGUgcGxvdHMgaW4gcGxvdF9saXN0X2NhdA0KZm9yICh2YXIgaW4gbmFtZXMoY2F0ZWdvcmljYWxfdmFycykpIHsNCiAgIyBDaGVjayBpZiB0aGUgdmFyaWFibGUgaXMgY2F0ZWdvcmljYWwNCiAgaWYgKGlzLmZhY3RvcihoZWFydHNbW3Zhcl1dKSB8fCBpcy5jaGFyYWN0ZXIoaGVhcnRzW1t2YXJdXSkpIHsNCiAgICBwIDwtIGdncGxvdChoZWFydHMsIGFlc19zdHJpbmcoeCA9IHZhcikpICsNCiAgICAgIGdlb21fYmFyKGZpbGwgPSAic2t5Ymx1ZSIsIGNvbG9yID0gImJsYWNrIiwgYWxwaGEgPSAwLjcpICsNCiAgICAgIHRoZW1lX21pbmltYWwoKSArDQogICAgICBsYWJzKHRpdGxlID0gcGFzdGUoIkRpc3RyaWJ1dGlvbiBvZiIsIHZhciksIHggPSB2YXIsIHkgPSAiQ291bnQiKSArDQogICAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KICAgIA0KICAgICMgQ29udmVydCBnZ3Bsb3QgdG8gcGxvdGx5IGludGVyYWN0aXZlIHBsb3QNCiAgICBwbG90X2xpc3RfY2F0W1t2YXJdXSA8LSBnZ3Bsb3RseShwKQ0KICB9DQp9DQoNCiMgQ3JlYXRlIGluZGl2aWR1YWwgaW50ZXJhY3RpdmUgcGFuZWxzIGZvciBlYWNoIGNhdGVnb3JpY2FsIHBsb3QNCmZvciAocCBpbiBwbG90X2xpc3RfY2F0KSB7DQogIHByaW50KHApDQp9DQpgYGANCg0KKC4uLi4uLi4uLi4uLi4uLi4uDQogICkNCiAgDQojIyMgUmVsYXRpb25zaGlwIGJldHdlZW4gRmVhdHVyZXMNCg0KTm93IHRoYXQgd2UgaGF2ZSBsb29raW5nIHRoZSB0aGUgZmVhdHVyZXMgaW5kaXZpZHVhbGx5LCB3ZSBjYW4gZG8gc29tZSBhbmFseXNpcyB0byBmaW5kIGluc2lnaHRzIGJldHdlZW4gdGhlIHJlbGF0aW9uc2hpcHMgd2l0aCBvdGhlciBmZWF0dXJlcy4gVGhpcyBpbnNpZ2h0cyBjYW4gcHJvdmlkZSBjbGFyaXR5IG9uIHdoYXQgZnV0dXJlIGFuYWx5c2lzIHNob3VsZCBmb2N1cyB0aGUgYXR0ZW50aW9uIG9uLiBUaGlzIGV4cGxvcmF0b3J5IGFuYWx5c2lzIHdpbGwgcGVyZm9ybSB0aHJlZSByZWxhdGlvbnNoaXAgYmV0d2VlbjoNCiAgMS4gdHdvIG51bWVyaWNhbCB2YXJpYWJsZXMNCiAgMi4gdHdvIGNhdGVnb3JpY2FsIHZhcmlhYmxlcw0KICAzLiBvbmUgbnVtZXJpY2FsIGFuZCBvbmUgY2F0ZWdvcmljYWwgdmFyaWFibGUuDQogIA0KQWxsIGluc2lnaHRzIHdpbGwgYmUgZXhwbGFpbiB0aHJvdWdoIHZpc3VhbHMgYW5kIHRleHQuDQoNCkZpcnN0IHdlIHdpbGwgZXhwbG9yZSB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdHdvIG51bWVyaWNhbCB2YXJpYWJsZXMsIGdsdWNvc2UgbGV2ZWxzIGFuZCBhZ2UuDQoNCmBgYHtyLCBmaWcuYWxpZ249J2NlbnRlcicsIHdhcm5pbmdzPUZBTFNFfQ0KDQojIElmICdhZ2UnIG9yICdnbHVjb3NlJyBhcmUgbm90IG51bWVyaWMsIGNvbnZlcnQgdGhlbQ0KaGVhcnRzX2NsZWFuJGFnZSA8LSBhcy5udW1lcmljKGhlYXJ0c19jbGVhbiRhZ2UpDQpoZWFydHNfY2xlYW4kZ2x1Y29zZSA8LSBhcy5udW1lcmljKGhlYXJ0c19jbGVhbiRnbHVjb3NlKQ0KDQoNCnBsb3QxX2hlYXJ0IDwtIGdncGxvdChoZWFydHNfY2xlYW4sIGFlcyh4ID0gYWdlLCB5ID0gZ2x1Y29zZSkpICsNCiAgZ2VvbV9wb2ludCgpICsgIA0KICB5bGFiKCJHbHVjb3NlIikgKyAgDQogIHhsYWIoIkFnZSIpICsgIA0KICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCAxMDApKSArICANCiAgdGhlbWVfbWluaW1hbCgpDQoNCnBsb3QxX2hlYXJ0DQpgYGANCkZyb20gdGhpcyBzY2F0dGVycGxvdCwgdGhlcmUgZG9lcyBub3Qgc2VlbSB0byBiZSBhbiBvYnZpb3VzIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGdsdWNvc2UgYW5kIGFnZS4gQnV0LCB3ZSBtdXN0IGludmVzdGlnYXRlIGZ1cnRoZXIgaWYgdGhlcmUgaXMgc29tZSBpbnRlcmFjdGlvbiB3aXRoIG90aGVyIHZhcmlibGVzLg0KDQpOb3cgbG9va2luZyBhdCB0d28gY2F0ZWdvcmljYWwgdmFyaWFibGVzIHdlIGNhbiBhbmFseXplIHRoZW0gdmlzdWFsbHkgdGhyb3VnaCBhIHN0YWNrZWQgYmFyIHBsb3QuIEhlcmUgd2Ugd2lsbCBsb29rIGF0IHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0ZW4geWVhciBjaGQgYW5kIGVkdWNhdGlvbg0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInLCB3YXJuaW5ncz1GQUxTRX0NCg0KZGZfcGVyY2VudF9oZWFydHMgPC0gaGVhcnRzX2NsZWFuICU+JQ0KICBncm91cF9ieShlZHVjYXRpb24sIFRlblllYXJDSEQpICU+JQ0KICBzdW1tYXJpc2UoY291bnQgPSBuKCkpICU+JQ0KICBncm91cF9ieShlZHVjYXRpb24pICU+JQ0KICBtdXRhdGUocGVyY2VudCA9IGNvdW50IC8gc3VtKGNvdW50KSAqIDEwMCkNCg0KIyBDcmVhdGUgdGhlIHN0YWNrZWQgYmFyIHBsb3Qgd2l0aCBwZXJjZW50YWdlcw0KZ2dwbG90KGRmX3BlcmNlbnRfaGVhcnRzLCBhZXMoeCA9IGVkdWNhdGlvbiwgeSA9IHBlcmNlbnQsIGZpbGwgPSBUZW5ZZWFyQ0hEKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKw0KICBsYWJzKHRpdGxlID0gIlN0YWNrZWQgQmFyIFBsb3Qgb2YgRWR1Y2F0aW9uIGJ5IENIRCIsDQogICAgICAgeCA9ICJFZHVjYXRpb24gTGV2ZWwiLA0KICAgICAgIHkgPSAiUGVyY2VudGFnZSIpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygicmVkIiwgImJsdWUiKSkgKyAgIyBBZGp1c3QgY29sb3JzIGFzIG5lZWRlZA0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCg0KYGBgDQoNCkZyb20gdGhlIHN0YWNrZWQgYmFyIGNoYXJ0LCBvbmNlIGFnYWluIHdlIGNhbiBzZWUgdGhhdCB0aGVyZSBpcyBub3QgYSBzaWduaWZpY2FudCByZWxhdGlvbnNoaXAgYmV0d2VlbiBlZHVjYXRpb24gYW5kICBhbmQgaGF2aW5nIENIRC4NCg0KDQpGaW5hbGx5IHdlIHdpbGwgZXhwbG9yZSB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gb25lIG51bWVyaWNhbCB2YXJpYWJsZSwgaGVhcnRSYXRlLCB3aXRoIGEgY2F0ZWdvcmljYWwgZmVhdHVyZSwgVGVuWWVhckNIRC4gVGhpcyB3aWxsIGJlIGV4cGxvcmVkIHVzaW5nIGJveHBsb3RzLg0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInLCB3YXJuaW5ncz1GQUxTRX0NCmdncGxvdChoZWFydHMsIGFlcyh4ID0gZWR1Y2F0aW9uLCB5ID0gaGVhcnRSYXRlKSkgKw0KICBnZW9tX2JveHBsb3QoKSArDQogIGxhYnModGl0bGUgPSAiQm94IFBsb3Qgb2YgRWR1Y2F0aW9uIGFuZCBIZWFydFJhdGUiLA0KICAgICAgIHggPSAiSGVhcnRSYXRlIiwNCiAgICAgICB5ID0gIkVkdWNhdGlvbiIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KSGVyZSB3ZSBjYW4gc2VlIHRoYXQgYXJlIGRlZmluaXRlbHkgc29tZSBvdXRsaWVycyB3aXRoIHRoZSBkYXRhLCBidXQgdGhhdCBjb3VsZCBiZSBkdWUgdG8gb3RoZXIgZmFjdG9ycyBhbmQgbXVzdCBiZSBleHBsb3JlZCBmdXJ0aGVyLg0KDQoNCiMjIDMuIEZlYXV0dXJlIEVuZ2luZWVyaW5nDQoNCg0KICBJdCB3YXMgc2hvd24gdGhhdCB0aGVyZSB3ZXJlIHNvbWUgcG9zaXRpdmVseSBza2V3ZWQgZGlzdHJpYnV0aW9ucyBzbyB0aGVzZSBzaG91bGQgYmUgdHJhbnNmb3JtZWQgdXNpbmcgYSBsb2cgdHJhbnNmb3JtYXRpb24uIFRoZSBjb2RlIGZvciB0aGVzZSB0cmFuc2Zvcm1hdGlvbnMgYW5kIHRoZWlyIHJlc3VsdGluZyBncmFwaHMgYXJlIHNob3duIGJlbG93Lg0KIA0KIA0KYGBge3IsIHdhcm5pbmdzPUZBTFNFfQ0KDQojIENvbnZlcnQgcmVsZXZhbnQgY29sdW1ucyB0byBudW1lcmljIGlmIHRoZXkgYXJlIGZhY3RvcnMNCmhlYXJ0c19jbGVhbiRhZ2UgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoaGVhcnRzX2NsZWFuJGFnZSkpDQpoZWFydHNfY2xlYW4kdG90Q2hvbCA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihoZWFydHNfY2xlYW4kdG90Q2hvbCkpDQpoZWFydHNfY2xlYW4kQk1JIDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKGhlYXJ0c19jbGVhbiRCTUkpKQ0KaGVhcnRzX2NsZWFuJGdsdWNvc2UgPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoaGVhcnRzX2NsZWFuJGdsdWNvc2UpKQ0KaGVhcnRzX2NsZWFuJGhlYXJ0UmF0ZSA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihoZWFydHNfY2xlYW4kaGVhcnRSYXRlKSkNCmhlYXJ0c19jbGVhbiRkaWFiZXRlcyA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihoZWFydHNfY2xlYW4kZGlhYmV0ZXMpKQ0KDQojIEFwcGx5IHRyYW5zZm9ybWF0aW9ucyBhZnRlciBlbnN1cmluZyB0aGUgdmFyaWFibGVzIGFyZSBudW1lcmljDQpoZWFydHNfY2xlYW4gPC0gaGVhcnRzX2NsZWFuICU+JQ0KICBtdXRhdGUoDQogICAgYWdlX2xvZyA9IGxvZzFwKGFnZSksICAgICAgICAgIyBsb2cgdHJhbnNmb3JtYXRpb24gb2YgYWdlDQogICAgdG90Q2hvbF9sb2cgPSBsb2cxcCh0b3RDaG9sKSwgIyBsb2cgdHJhbnNmb3JtYXRpb24gb2YgdG90Q2hvbA0KICAgIEJNSV9zcXJ0ID0gc3FydChCTUkpLCAgICAgICAgICMgc3F1YXJlIHJvb3QgdHJhbnNmb3JtYXRpb24gb2YgQk1JDQogICAgZ2x1Y29zZV9zcXJ0ID0gc3FydChnbHVjb3NlKSwgIyBzcXVhcmUgcm9vdCB0cmFuc2Zvcm1hdGlvbiBvZiBnbHVjb3NlDQogICAgaGVhcnRSYXRlX2ludiA9IDEgLyAoaGVhcnRSYXRlICsgMSksICAjIGludmVyc2UgdHJhbnNmb3JtYXRpb24gb2YgaGVhcnRSYXRlDQogICAgZGlhYmV0ZXNfaW52ID0gMSAvIChkaWFiZXRlcyArIDEpICAgICAjIGludmVyc2UgdHJhbnNmb3JtYXRpb24gb2YgZGlhYmV0ZXMNCiAgKQ0KYGBgDQoNCg0KDQojIyA0LiBGZWF0dXJlIFNlbGVjdGlvbiBhbmQgQ3JlYXRpb24NCg0KQWJvdmUgd2UgaGF2ZSBpZGVudGlmaWVkIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyB0aGF0IHNob3VsZCBiZSByZWdyb3VwZWQgdG8gc2ltcGxpZnkgb3VyIG1vZGVsLiBSZWdyb3VwaW5nIHNpbXBsaWZpZXMgb3VyIG1vZGVsLCB3aGljaCBpbXByb3ZlcyBvdXIgcG93ZXIuDQoNClRoZXNlIGNhbiBiZSBzaG93biBiZWluZyByZWdyb3VwZWQgYmVsb3c6DQoNCmBgYHtyLCBmaWcuYWxpZ249J2NlbnRlcicsIHdhcm5pbmdzPUZBTFNFfQ0KIyBSZWdyb3VwaW5nIEFnZSBpbnRvIGNhdGVnb3JpZXMNCmhlYXJ0c19maWx0ZXJlZCA8LSBoZWFydHNfY2xlYW4gJT4lDQogIG11dGF0ZShhZ2VfZ3JvdXAgPSBjYXNlX3doZW4oDQogICAgYWdlIDw9IDMwIH4gIlVuZGVyIDMwIiwNCiAgICBhZ2UgPD0gNTAgfiAiMzAtNTAiLA0KICAgIGFnZSA8PSA3MCB+ICI1MS03MCIsDQogICAgYWdlID4gNzAgfiAiQWJvdmUgNzAiDQogICkpDQoNCiMgUmVncm91cGluZyBFZHVjYXRpb24gaW50byBjYXRlZ29yaWVzIChhc3N1bWluZyB5b3UgaGF2ZSBlZHVjYXRpb24gZGF0YSBjb2RlZCB3aXRoIG51bWJlcnMgb3Igc3BlY2lmaWMgY2F0ZWdvcmllcykNCmhlYXJ0cyA8LSBoZWFydHNfZmlsdGVyZWQgJT4lDQogIG11dGF0ZShlZHVjYXRpb25fZ3JvdXAgPSBjYXNlX3doZW4oDQogICAgZWR1Y2F0aW9uID09IDEgfiAiTG93IiwgICMgSWYgIjEiIG1lYW5zIGxvdyBlZHVjYXRpb24gbGV2ZWwNCiAgICBlZHVjYXRpb24gPT0gMiB+ICJNZWRpdW0iLCAgIyBJZiAiMiIgbWVhbnMgbWVkaXVtIGVkdWNhdGlvbiBsZXZlbA0KICAgIGVkdWNhdGlvbiA9PSAzIH4gIkhpZ2giICAgIyBJZiAiMyIgbWVhbnMgaGlnaCBlZHVjYXRpb24gbGV2ZWwNCiAgKSkNCg0KIyBSZWdyb3VwaW5nIEJQIE1lZGljYXRpb25zIChCUE1lZHMpIGludG8gY2F0ZWdvcmllcw0KaGVhcnRzIDwtIGhlYXJ0c19maWx0ZXJlZCAlPiUNCiAgbXV0YXRlKEJQTWVkc19ncm91cCA9IGNhc2Vfd2hlbigNCiAgICBCUE1lZHMgPT0gMCB+ICJObyBNZWRpY2F0aW9uIiwNCiAgICBCUE1lZHMgPT0gMSB+ICJPbiBNZWRpY2F0aW9uIg0KICApKQ0KDQojIENoZWNrIHRoZSByZXN1bHRzDQpoZWFkKGhlYXJ0cykNCg0KYGBgDQpOb3cgbGV0J3MgbW92ZSBpbiBkaXNjcmVjdGl6aW5nIHNvbWUgb2Ygb3VyIG51bWVyaWNhbCB2YXJpYWJsZXMuIFRoaXMgaXMgdXNlZnVsIGZvciBtdWx0aXBsZSByZWFzb25zLCBpbmNsdWRpbmcgaW1wcm92aW5nIG1vZGVsIGludGVycHJldGFiaWxpdHksIGhhbmRsaW5nIG5vbi1saW5lYXJpdHksIGFuZCByZWR1Y2luZyBub2lzeSBkYXRhLiBUaGlzIG9zIGdyb3VwaW5nIGNvbnRpbnVvdXMgZGF0YS4gU29tZSBvZiB0aGUgdmFyaWFibGVzIHdlIGFyZSBnb2luZyB0byBkaXNjcmV0aXplIGFyZSBhZ2UsIGluY29tZSBhbmQgaW50ZXJlc3QgcmF0ZXMuDQoNCkJlbG93IGlzIHRoZSBjb2RlIGluIG9yZGVyIHRvIGRpc2NyZXRpemUgZGF0YQ0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInLCB3YXJuaW5ncz1GQUxTRX0NCmhlYXJ0cyA8LSBoZWFydHNfY2xlYW4gJT4lDQogICMgRGlzY3JldGl6ZSBBZ2UNCiAgbXV0YXRlKGFnZV9ncm91cCA9IGNhc2Vfd2hlbigNCiAgICBhZ2UgPD0gMzAgfiAiWW91bmciLA0KICAgIGFnZSA+IDMwICYgYWdlIDw9IDUwIH4gIk1pZGRsZS1BZ2VkIiwNCiAgICBhZ2UgPiA1MCB+ICJPbGRlciIsDQogICAgVFJVRSB+IE5BX2NoYXJhY3Rlcl8gICMgSGFuZGxlIHVuZXhwZWN0ZWQgdmFsdWVzDQogICkpICU+JQ0KDQogICMgRGlzY3JldGl6ZSBCTUkNCiAgbXV0YXRlKGJtaV9ncm91cCA9IGNhc2Vfd2hlbigNCiAgICBCTUkgPCAxOC41IH4gIlVuZGVyd2VpZ2h0IiwNCiAgICBCTUkgPj0gMTguNSAmIEJNSSA8IDI1IH4gIk5vcm1hbCBXZWlnaHQiLA0KICAgIEJNSSA+PSAyNSAmIEJNSSA8IDMwIH4gIk92ZXJ3ZWlnaHQiLA0KICAgIEJNSSA+PSAzMCB+ICJPYmVzZSIsDQogICAgVFJVRSB+IE5BX2NoYXJhY3Rlcl8NCiAgKSkgJT4lDQoNCiAgIyBEaXNjcmV0aXplIEdsdWNvc2UgbGV2ZWxzDQogIG11dGF0ZShnbHVjb3NlX2dyb3VwID0gY2FzZV93aGVuKA0KICAgIGdsdWNvc2UgPCAxMDAgfiAiTm9ybWFsIiwNCiAgICBnbHVjb3NlID49IDEwMCAmIGdsdWNvc2UgPCAxMjYgfiAiUHJlLWRpYWJldGljIiwNCiAgICBnbHVjb3NlID49IDEyNiB+ICJEaWFiZXRpYyIsDQogICAgVFJVRSB+IE5BX2NoYXJhY3Rlcl8NCiAgKSkgJT4lDQoNCiAgIyBEaXNjcmV0aXplIENob2xlc3Rlcm9sIGxldmVscyAoVG90Q2hvbCkNCiAgbXV0YXRlKGNob2xlc3Rlcm9sX2dyb3VwID0gY2FzZV93aGVuKA0KICAgIHRvdENob2wgPCAyMDAgfiAiRGVzaXJhYmxlIiwNCiAgICB0b3RDaG9sID49IDIwMCAmIHRvdENob2wgPCAyNDAgfiAiQm9yZGVybGluZSBIaWdoIiwNCiAgICB0b3RDaG9sID49IDI0MCB+ICJIaWdoIiwNCiAgICBUUlVFIH4gTkFfY2hhcmFjdGVyXw0KICApKQ0KDQojIENvbnZlcnQgbmV3IGNvbHVtbnMgdG8gZmFjdG9ycw0KDQojIENoZWNrIHRoZSBmaXJzdCBmZXcgcm93cw0KaGVhZChoZWFydHMpDQpgYGANCg0KRmluYWxseSwgd2Ugd2FudCB0byBmaWx0ZXIgYWxsIG91ciBkYXRhIGludG8gIG9uZSBjbGVhbmVkIGRhdGFzZXQuIFdlIG9ubHkgd2FudCBvdXIgZGlzY3JldGl6ZWQgdmFyaWFibGVzIGFuZCB0aG9zZSBkb21haW4gdmFyaWFibGVzIHdlIGRpc2N1c3NlZCBlYXJsaWVyLiBCZWxvdyB0aGUgY29kZSB0byBmaWx0ZXIgdGhlIGZpbmFsIGRhdGFzZXQgaXMgc2VlbiBiZWxvdy4gVGhpcyBkYXRhIGlzIG5vdyBhYmxlIHRvIGJlIHVzZWQgZm9yIGZ1dHVyZSBhbmFseXNpcy4NCg0KYGBge3IsIGZpZy5hbGlnbj0nY2VudGVyJywgd2FybmluZ3M9RkFMU0V9DQpsaWJyYXJ5KGRwbHlyKQ0KI2NvbG5hbWVzKGhlYXJ0cykNCiMgRmlsdGVyIHRoZSBkYXRhc2V0IHRvIGluY2x1ZGUgb25seSB0aGUgc3BlY2lmaWVkIHZhcmlhYmxlcw0KaGVhcnRzX2ZpbmFsIDwtIGhlYXJ0cyAlPiUNCiAgc2VsZWN0KG1hbGUsIGFnZV9sb2csIGVkdWNhdGlvbiwgY3VycmVudFNtb2tlciwgY2lnc1BlckRheSwgQlBNZWRzLCANCiAgICAgICAgIHByZXZhbGVudFN0cm9rZSwgcHJldmFsZW50SHlwLCB0b3RDaG9sX2xvZywgQk1JX3NxcnQsIGdsdWNvc2UsIGhlYXJ0UmF0ZV9pbnYsIFRlblllYXJDSEQpDQoNCiMgVmlldyB0aGUgZmlsdGVyZWQgZGF0YXNldA0KaGVhZChoZWFydHNfZmluYWwpDQpgYGANCg0KIyMgNS4gUmVndWxhcmxpemVkIFJlZ3Jlc3Npb24NCg0KTGFzc28gKExlYXN0IEFic29sdXRlIFNocmlua2FnZSBhbmQgU2VsZWN0aW9uIE9wZXJhdG9yKSByZWdyZXNzaW9uIGFwcGxpZXMgTDEgcmVndWxhcml6YXRpb24sIGVuY291cmFnaW5nIHNwYXJzaXR5IGluIHRoZSBtb2RlbCBieSBkcml2aW5nIHNvbWUgY29lZmZpY2llbnRzIHRvIHplcm8sIGVmZmVjdGl2ZWx5IHBlcmZvcm1pbmcgZmVhdHVyZSBzZWxlY3Rpb24uIFJpZGdlIHJlZ3Jlc3Npb24sIG9uIHRoZSBvdGhlciBoYW5kLCB1c2VzIEwyIHJlZ3VsYXJpemF0aW9uLCB3aGljaCBwZW5hbGl6ZXMgbGFyZ2UgY29lZmZpY2llbnRzIGJ1dCBkb2VzIG5vdCBlbGltaW5hdGUgdmFyaWFibGVzLiBFbGFzdGljIE5ldCBjb21iaW5lcyBib3RoIEwxIGFuZCBMMiByZWd1bGFyaXphdGlvbiwgYmFsYW5jaW5nIHRoZSBzdHJlbmd0aHMgb2YgTGFzc28gYW5kIFJpZGdlLiBCeSBjb21wYXJpbmcgdGhlc2UgdGhyZWUgYXBwcm9hY2hlcywgd2UgY2FuIGlkZW50aWZ5IHRoZSBtb3N0IGVmZmVjdGl2ZSBtb2RlbCBmb3IgcHJlZGljdGluZyBnbHVjb3NlIGxldmVscywgd2hpbGUgYWxzbyBpbXByb3ZpbmcgbW9kZWwgaW50ZXJwcmV0YWJpbGl0eSBhbmQgZ2VuZXJhbGl6YWJpbGl0eS4NCg0KIyMjIENvZWZmaWNhbnQgUGF0aCBBbmFseXNpcw0KDQpDb2VmZmljaWVudCBwYXRoIGFuYWx5c2lzIGlzIGEgcG93ZXJmdWwgdGVjaG5pcXVlIHVzZWQgdG8gdmlzdWFsaXplIGFuZCBpbnRlcnByZXQgaG93IHRoZSBjb2VmZmljaWVudHMgb2YgYSByZWdyZXNzaW9uIG1vZGVsIGNoYW5nZSBhcyBhIHJlZ3VsYXJpemF0aW9uIHBhcmFtZXRlciAoc3VjaCBhcyBsYW1iZGEgaW4gTGFzc28sIFJpZGdlLCBvciBFbGFzdGljIE5ldCByZWdyZXNzaW9uKSBpcyB2YXJpZWQuIFRoaXMgbWV0aG9kIHByb3ZpZGVzIGluc2lnaHQgaW50byB0aGUgc3RhYmlsaXR5IG9mIGVhY2ggcHJlZGljdG9yIHZhcmlhYmxlJ3MgY29udHJpYnV0aW9uIHRvIHRoZSBtb2RlbCwgaGVscGluZyB0byBpZGVudGlmeSB3aGljaCBmZWF0dXJlcyBhcmUgbW9zdCBpbmZsdWVudGlhbCBpbiBwcmVkaWN0aW5nIHRoZSB0YXJnZXQgdmFyaWFibGUuIA0KDQpgYGB7ciwgd2FybmluZ3M9RkFMU0V9DQpsaWJyYXJ5KGdsbW5ldCkNCmxpYnJhcnkoY2FyZXQpDQoNCiMgUHJlcGFyZSB0aGUgZGF0YQ0KIyBSZW1vdmUgcm93cyB3aXRoIG1pc3NpbmcgdGFyZ2V0IHZhcmlhYmxlICdnbHVjb3NlJw0KaGVhcnRzX2ZpbmFsIDwtIGhlYXJ0c19maW5hbFshaXMubmEoaGVhcnRzX2ZpbmFsJGdsdWNvc2UpLCBdDQoNCiMgUmVkZWZpbmUgWCAocHJlZGljdG9ycykgYW5kIHkgKHRhcmdldCkNClggPC0gaGVhcnRzX2ZpbmFsWywgLXdoaWNoKG5hbWVzKGhlYXJ0c19maW5hbCkgPT0gImdsdWNvc2UiKV0gICMgQWxsIHByZWRpY3RvcnMgZXhjZXB0ICdnbHVjb3NlJw0KeSA8LSBoZWFydHNfZmluYWwkZ2x1Y29zZSAgIyBUYXJnZXQgdmFyaWFibGUgJ2dsdWNvc2UnDQoNCiMgU3BsaXQgdGhlIGRhdGEgaW50byB0cmFpbmluZyBhbmQgdGVzdGluZyBzZXRzICg4MCUgdHJhaW4sIDIwJSB0ZXN0KQ0Kc2V0LnNlZWQoMTIzNCkgICMgU2V0IHNlZWQgZm9yIHJlcHJvZHVjaWJpbGl0eQ0KdHJhaW5faW5kZXggPC0gY3JlYXRlRGF0YVBhcnRpdGlvbih5LCBwID0gMC44LCBsaXN0ID0gRkFMU0UpDQpYX3RyYWluIDwtIFhbdHJhaW5faW5kZXgsIF0NClhfdGVzdCA8LSBYWy10cmFpbl9pbmRleCwgXQ0KeV90cmFpbiA8LSB5W3RyYWluX2luZGV4XQ0KeV90ZXN0IDwtIHlbLXRyYWluX2luZGV4XQ0KDQojIEVuc3VyZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgYXJlIGNvbnZlcnRlZCB0byBudW1lcmljIChkdW1teS9pbmRpY2F0b3IgdmFyaWFibGVzKQ0KIyBDb252ZXJ0IFhfdHJhaW4gYW5kIFhfdGVzdCB0byBudW1lcmljIG1hdHJpeCB3aXRoIG1vZGVsLm1hdHJpeA0KWF90cmFpbiA8LSBtb2RlbC5tYXRyaXgofiAuIC0gMSwgZGF0YSA9IGFzLmRhdGEuZnJhbWUoWF90cmFpbikpICAjIFJlbW92ZSBpbnRlcmNlcHQgKC0xKQ0KWF90ZXN0IDwtIG1vZGVsLm1hdHJpeCh+IC4gLSAxLCBkYXRhID0gYXMuZGF0YS5mcmFtZShYX3Rlc3QpKSAgIyBSZW1vdmUgaW50ZXJjZXB0ICgtMSkNCg0KIyBBbGlnbiBjb2x1bW5zIG9mIFhfdHJhaW4gYW5kIFhfdGVzdCB0byBoYXZlIHRoZSBzYW1lIHZhcmlhYmxlcw0KIyBUaGlzIGVuc3VyZXMgdGhhdCBib3RoIHRyYWluaW5nIGFuZCB0ZXN0aW5nIGRhdGFzZXRzIGhhdmUgdGhlIHNhbWUgc2V0IG9mIGNvbHVtbnMNClhfdGVzdCA8LSBYX3Rlc3RbLCBjb2xuYW1lcyhYX3RyYWluKSwgZHJvcCA9IEZBTFNFXQ0KDQojIEZpdCBMYXNzbyAoYWxwaGEgPSAxKSwgUmlkZ2UgKGFscGhhID0gMCksIGFuZCBFbGFzdGljIE5ldCAoYWxwaGEgPSAwLjUpDQpmaXRfbGFzc28gPC0gZ2xtbmV0KFhfdHJhaW4sIHlfdHJhaW4sIGFscGhhID0gMSkNCmZpdF9yaWRnZSA8LSBnbG1uZXQoWF90cmFpbiwgeV90cmFpbiwgYWxwaGEgPSAwKQ0KZml0X2VsYXN0aWNfbmV0IDwtIGdsbW5ldChYX3RyYWluLCB5X3RyYWluLCBhbHBoYSA9IDAuNSkNCg0KIyBDcm9zcy12YWxpZGF0aW9uIGZvciBMYXNzbywgUmlkZ2UsIGFuZCBFbGFzdGljIE5ldA0KY3ZfbGFzc28gPC0gY3YuZ2xtbmV0KFhfdHJhaW4sIHlfdHJhaW4sIGFscGhhID0gMSkNCmN2X3JpZGdlIDwtIGN2LmdsbW5ldChYX3RyYWluLCB5X3RyYWluLCBhbHBoYSA9IDApDQpjdl9lbGFzdGljX25ldCA8LSBjdi5nbG1uZXQoWF90cmFpbiwgeV90cmFpbiwgYWxwaGEgPSAwLjUpDQoNCiMgUGxvdCBjcm9zcy12YWxpZGF0aW9uIHJlc3VsdHMNCnBhcihtZnJvdyA9IGMoMSwgMykpICAjIEFycmFuZ2UgcGxvdHMgaW4gYSByb3cNCnBsb3QoY3ZfbGFzc28pDQpwbG90KGN2X3JpZGdlKQ0KcGxvdChjdl9lbGFzdGljX25ldCkNCg0KIyBCZXN0IGxhbWJkYSBmb3IgZWFjaCBtb2RlbA0KbGFtYmRhX2xhc3NvIDwtIGN2X2xhc3NvJGxhbWJkYS5taW4NCmxhbWJkYV9yaWRnZSA8LSBjdl9yaWRnZSRsYW1iZGEubWluDQpsYW1iZGFfZWxhc3RpY19uZXQgPC0gY3ZfZWxhc3RpY19uZXQkbGFtYmRhLm1pbg0KDQojIFByZWRpY3QgdXNpbmcgdGhlIGJlc3QgbGFtYmRhIGZyb20gZWFjaCBtb2RlbA0KcHJlZF9sYXNzbyA8LSBwcmVkaWN0KGZpdF9sYXNzbywgWF90ZXN0LCBzID0gbGFtYmRhX2xhc3NvLCB0eXBlID0gInJlc3BvbnNlIikNCnByZWRfcmlkZ2UgPC0gcHJlZGljdChmaXRfcmlkZ2UsIFhfdGVzdCwgcyA9IGxhbWJkYV9yaWRnZSwgdHlwZSA9ICJyZXNwb25zZSIpDQpwcmVkX2VsYXN0aWNfbmV0IDwtIHByZWRpY3QoZml0X2VsYXN0aWNfbmV0LCBYX3Rlc3QsIHMgPSBsYW1iZGFfZWxhc3RpY19uZXQsIHR5cGUgPSAicmVzcG9uc2UiKQ0KDQpgYGANCg0KV2UgY2FuIHNlZSB0aGF0IHRoZXJlIGFyZSBzb21lIGluc2lnbmlmaWNhbnQgcHJlZGljdG9yIHZhcmlhYmxlcywgYW5kIHRoZXkgc2hvdWxkIGJlIGRyb3BwZWQgZnJvbSB0aGUgbW9kZWwuIFVzaW5nIHRoZSBzdGVwKCkgZnVuY3Rpb24sIHdlIHdpbGwgbm93IGZpbmQgIHRoZSBmaW5hbCBtb2RlbC4gVGhlIGZpbmFsIGJlc3QgbW9kZWwgd2lsbCBiZSBhIG1vZGVsIHRoYXQgaXMgYmV0d2VlbiB0aGUgZnVsbCBhbmQgcmVkdWNlZCBtb2RlbHMuDQoNCmBgYHtyLCB3YXJuaW5ncz1GQUxTRX0NCmxpYnJhcnkoZ2xtbmV0KQ0KDQojIFBsb3QgdGhlIGNvZWZmaWNpZW50IHBhdGgNCnBhcihtYXI9Yyg1LDQsNiwzKSkgICMgQWRqdXN0IG1hcmdpbnMgdG8gZml0IHRoZSB0aXRsZQ0KcGxvdChmaXRfbGFzc28sIHh2YXIgPSAibGFtYmRhIiwgbGFiZWwgPSBUUlVFLCANCiAgICAgbHdkID0gMS41LCANCiAgICAgbWFpbiA9ICJDb2VmZmljaWVudCBQYXRoIEFuYWx5c2lzOiBMQVNTTyAoSGVhcnRzIERhdGFzZXQpIiwNCiAgICAgY2V4Lm1haW4gPSAwLjksIA0KICAgICBjb2wgPSByYWluYm93KG5jb2woWCkpKSAgIyBDb2xvciBmb3IgZWFjaCBjb2VmZmljaWVudA0KYWJsaW5lKHYgPSAxLCBjb2wgPSAicHVycGxlIiwgbHR5ID0gNCwgbHdkID0gMikgICMgVmVydGljYWwgbGluZSBmb3IgbGFtYmRhID0gMQ0KYWJsaW5lKHYgPSAtMSwgY29sID0gInN0ZWVsYmx1ZSIsIGx0eSA9IDIsIGx3ZCA9IDIpICAjIFZlcnRpY2FsIGxpbmUgZm9yIGxhbWJkYSA9IC0xDQpgYGANCmBgYHtyLCB3YXJuaW5ncz1GQUxTRX0NCnBhcihtYXI9Yyg1LDQsNiwzKSkNCiMjDQpwbG90KGN2X2xhc3NvLCBtYWluID0gIlJNU0UgUGxvdDogTEFTU08iLA0KICAgICBjZXgubWFpbiA9IDAuOSkNCg0KIyBDYWxjdWxhdGUgUk1TRSBmb3IgZWFjaCBtb2RlbA0Kcm1zZV9sYXNzbyA8LSBzcXJ0KG1lYW4oKHByZWRfbGFzc28gLSB5X3Rlc3QpXjIpKQ0Kcm1zZV9yaWRnZSA8LSBzcXJ0KG1lYW4oKHByZWRfcmlkZ2UgLSB5X3Rlc3QpXjIpKQ0Kcm1zZV9lbGFzdGljX25ldCA8LSBzcXJ0KG1lYW4oKHByZWRfZWxhc3RpY19uZXQgLSB5X3Rlc3QpXjIpKQ0KDQpjYXQoIlJNU0UgZm9yIExhc3NvOiAiLCBybXNlX2xhc3NvLCAiXG4iKQ0KY2F0KCJSTVNFIGZvciBSaWRnZTogIiwgcm1zZV9yaWRnZSwgIlxuIikNCmNhdCgiUk1TRSBmb3IgRWxhc3RpYyBOZXQ6ICIsIHJtc2VfZWxhc3RpY19uZXQsICJcbiIpDQpgYGANCg0KIyMjIFR1bmluZyBQYXJhbWV0ZXINCg0KVHVuaW5nIHRoZSByZWd1bGFyaXphdGlvbiBwYXJhbWV0ZXIsIGxhbWJkYSwgaXMgYSBjcnVjaWFsIHN0ZXAgaW4gdGhlIHByb2Nlc3Mgb2YgZml0dGluZyBtb2RlbHMgdGhlc2UgbW9kZWxzLiBUaGlzIHBhcmFtZXRlciBjb250cm9scyB0aGUgc3RyZW5ndGggb2YgdGhlIHBlbmFsdHkgYXBwbGllZCB0byB0aGUgbW9kZWwsIGhlbHBpbmcgdG8gYXZvaWQgb3ZlcmZpdHRpbmcgYW5kIGltcHJvdmluZyBtb2RlbCBnZW5lcmFsaXphdGlvbi4gQSBsYXJnZSBsYW1iZGEgdmFsdWUgbGVhZHMgdG8gZ3JlYXRlciByZWd1bGFyaXphdGlvbiwgcmVzdWx0aW5nIGluIHNtYWxsZXIgY29lZmZpY2llbnRzLCB3aGlsZSBhIHNtYWxsZXIgbGFtYmRhIHZhbHVlIGFsbG93cyB0aGUgbW9kZWwgdG8gZml0IHRoZSB0cmFpbmluZyBkYXRhIG1vcmUgY2xvc2VseSwgcG90ZW50aWFsbHkgbGVhZGluZyB0byBvdmVyZml0dGluZy4NCg0KVG8gaWRlbnRpZnkgdGhlIGJlc3QgbGFtYmRhIGZvciBlYWNoIG1vZGVsLCB3ZSB1c2UgY3Jvc3MtdmFsaWRhdGlvbiAodmlhIHRoZSBjdi5nbG1uZXQgZnVuY3Rpb24pLCB3aGljaCBldmFsdWF0ZXMgdGhlIG1vZGVsIHBlcmZvcm1hbmNlIGFjcm9zcyBkaWZmZXJlbnQgdmFsdWVzIG9mIGxhbWJkYS4gVGhlIHByb2Nlc3MgaW52b2x2ZXMgc3BsaXR0aW5nIHRoZSBkYXRhIGludG8gdHJhaW5pbmcgYW5kIHZhbGlkYXRpb24gc2V0cyBtdWx0aXBsZSB0aW1lcyBhbmQgY2FsY3VsYXRpbmcgdGhlIHByZWRpY3Rpb24gZXJyb3IgZm9yIGVhY2ggY2FuZGlkYXRlIGxhbWJkYS4gVGhlIG9wdGltYWwgbGFtYmRhIGlzIHRoZSBvbmUgdGhhdCBtaW5pbWl6ZXMgdGhlIGNyb3NzLXZhbGlkYXRpb24gZXJyb3IsIGVuc3VyaW5nIHRoYXQgdGhlIG1vZGVsIGdlbmVyYWxpemVzIHdlbGwgdG8gdW5zZWVuIGRhdGEuDQpgYGB7ciwgd2FybmluZ3M9RkFMU0V9DQpsaWJyYXJ5KGdsbW5ldCkNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KHBhbmRlcikNCg0KDQoNCiMgRGVmaW5lIHRoZSBmZWF0dXJlcyAocHJlZGljdG9ycykgYW5kIHRhcmdldA0KWCA8LSBhcy5tYXRyaXgoaGVhcnRzX2ZpbmFsWywgLXdoaWNoKG5hbWVzKGhlYXJ0c19maW5hbCkgPT0gImdsdWNvc2UiKV0pICAjIEFsbCBmZWF0dXJlcyBleGNlcHQgdGFyZ2V0ICdnbHVjb3NlJw0KeSA8LSBoZWFydHNfZmluYWwkZ2x1Y29zZSAgIyBUYXJnZXQgdmFyaWFibGUgJ2dsdWNvc2UnDQoNCiMgU3BsaXQgdGhlIGRhdGEgaW50byB0cmFpbmluZyBhbmQgdGVzdGluZyBzZXRzDQpzZXQuc2VlZCgxMjMpICAjIFNldCBzZWVkIGZvciByZXByb2R1Y2liaWxpdHkNCnRyYWluX2luZGV4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oeSwgcCA9IDAuOCwgbGlzdCA9IEZBTFNFKQ0KWF90cmFpbiA8LSBYW3RyYWluX2luZGV4LCBdDQpYX3Rlc3QgPC0gWFstdHJhaW5faW5kZXgsIF0NCnlfdHJhaW4gPC0geVt0cmFpbl9pbmRleF0NCnlfdGVzdCA8LSB5Wy10cmFpbl9pbmRleF0NCg0KIyBDcm9zcy12YWxpZGF0aW9uIHRvIGZpbmQgdGhlIGJlc3QgbGFtYmRhIGZvciBlYWNoIG1vZGVsDQpjdl9sYXNzbyA8LSBjdi5nbG1uZXQoWF90cmFpbiwgeV90cmFpbiwgYWxwaGEgPSAxKQ0KY3ZfcmlkZ2UgPC0gY3YuZ2xtbmV0KFhfdHJhaW4sIHlfdHJhaW4sIGFscGhhID0gMCkNCmN2X2VsYXN0aWNfbmV0IDwtIGN2LmdsbW5ldChYX3RyYWluLCB5X3RyYWluLCBhbHBoYSA9IDAuNSkNCg0KIyBFeHRyYWN0IHRoZSBiZXN0IGxhbWJkYSB2YWx1ZXMgZm9yIGVhY2ggbW9kZWwNCmJlc3QubGFzc28ubGFtYmRhIDwtIGN2X2xhc3NvJGxhbWJkYS5taW4NCmJlc3QucmlkZ2UubGFtYmRhIDwtIGN2X3JpZGdlJGxhbWJkYS5taW4NCmJlc3QuZWxhc3RpYy5uZXQubGFtYmRhIDwtIGN2X2VsYXN0aWNfbmV0JGxhbWJkYS5taW4NCg0KIyBMYXNzbyBSZWdyZXNzaW9uIChMMSBSZWd1bGFyaXphdGlvbikNCmxhc3NvX21vZGVsLm9wdCA8LSBnbG1uZXQoWF90cmFpbiwgeV90cmFpbiwgYWxwaGEgPSAxLCBsYW1iZGEgPSBiZXN0Lmxhc3NvLmxhbWJkYSkNCmxhc3NvX3ByZWRpY3Rpb25zLm9wdCA8LSBwcmVkaWN0KGxhc3NvX21vZGVsLm9wdCwgcyA9IGJlc3QubGFzc28ubGFtYmRhLCBuZXd4ID0gWF90ZXN0KQ0KbGFzc29fcm1zZS5vcHQgPC0gc3FydChtZWFuKCh5X3Rlc3QgLSBsYXNzb19wcmVkaWN0aW9ucy5vcHQpXjIpKQ0KDQojIFJpZGdlIFJlZ3Jlc3Npb24gKEwyIFJlZ3VsYXJpemF0aW9uKQ0KcmlkZ2VfbW9kZWwub3B0IDwtIGdsbW5ldChYX3RyYWluLCB5X3RyYWluLCBhbHBoYSA9IDAsIGxhbWJkYSA9IGJlc3QucmlkZ2UubGFtYmRhKQ0KcmlkZ2VfcHJlZGljdGlvbnMub3B0IDwtIHByZWRpY3QocmlkZ2VfbW9kZWwub3B0LCBzID0gYmVzdC5yaWRnZS5sYW1iZGEsIG5ld3ggPSBYX3Rlc3QpDQpyaWRnZV9ybXNlLm9wdCA8LSBzcXJ0KG1lYW4oKHlfdGVzdCAtIHJpZGdlX3ByZWRpY3Rpb25zLm9wdCleMikpDQoNCiMgRWxhc3RpYyBOZXQgKENvbWJpbmF0aW9uIG9mIEwxIGFuZCBMMikNCmVsYXN0aWNfbmV0X21vZGVsLm9wdCA8LSBnbG1uZXQoWF90cmFpbiwgeV90cmFpbiwgYWxwaGEgPSAwLjUsIGxhbWJkYSA9IGJlc3QuZWxhc3RpYy5uZXQubGFtYmRhKQ0KZWxhc3RpY19uZXRfcHJlZGljdGlvbnMub3B0IDwtIHByZWRpY3QoZWxhc3RpY19uZXRfbW9kZWwub3B0LCBzID0gYmVzdC5lbGFzdGljLm5ldC5sYW1iZGEsIG5ld3ggPSBYX3Rlc3QpDQplbGFzdGljX25ldF9ybXNlLm9wdCA8LSBzcXJ0KG1lYW4oKHlfdGVzdCAtIGVsYXN0aWNfbmV0X3ByZWRpY3Rpb25zLm9wdCleMikpDQoNCiMgQ29tYmluZSBSTVNFIHZhbHVlcyBmb3IgY29tcGFyaXNvbg0KUk1TRS5vcHQgPSBjYmluZChMQVNTTy5vcHQgPSBsYXNzb19ybXNlLm9wdCwgDQogICAgICAgICAgICAgICAgIFJpZGdlLm9wdCA9IHJpZGdlX3Jtc2Uub3B0LCANCiAgICAgICAgICAgICAgICAgRWxhc3RpY25ldC5vcHQgPSBlbGFzdGljX25ldF9ybXNlLm9wdCkNCg0KIyBEaXNwbGF5IHRoZSByZXN1bHRzIHVzaW5nIHBhbmRlcg0KcGFuZGVyKFJNU0Uub3B0KQ0KYGBgDQoNCg0KDQojIyMgRXh0cmFjdGluZyBGaW5hbCBNb2RlbCBhbmQgUmVzdWx0cyANCg0KDQpUaGUgcmVzdWx0aW5nIExBU1NPIHJlZ3Jlc3Npb24gZXF1YXRpb24gaXMgZ2l2ZW4gYnkNCg0KYGBge3IsIHdhcm5pbmdzPUZBTFNFfQ0KIyBFeHRyYWN0IGNvZWZmaWNpZW50cyBmb3IgdGhlIGJlc3QgbGFtYmRhDQpiZXN0X2xhbWJkYS5sYXNzbyA8LSBjdl9sYXNzbyRsYW1iZGEubWluDQpjb2VmZmljaWVudHMubGFzc28gPC0gY29lZihjdl9sYXNzbywgcyA9IGJlc3RfbGFtYmRhLmxhc3NvKQ0KDQojIEV4dHJhY3QgdGhlIGludGVyY2VwdCBhbmQgYmV0YXMNCmludGVyY2VwdC5sYXNzbyA8LSBjb2VmZmljaWVudHMubGFzc29bMV0NCmJldGFzLmxhc3NvIDwtIGNvZWZmaWNpZW50cy5sYXNzb1stMV0NCg0KIyBSZWNvbnN0cnVjdCB0aGUgbW9kZWwgZXF1YXRpb24gYXMgYSBzdHJpbmcNCm1vZGVsX2VxdWF0aW9uIDwtIHBhc3RlKCJNb2RlbCBlcXVhdGlvbjogeSA9Iiwgcm91bmQoaW50ZXJjZXB0Lmxhc3NvLCA0KSwgDQogICAgICAgICAgICAgICAgICAgICAgICAiKyIsIHBhc3RlKHJvdW5kKGJldGFzLmxhc3NvLCA0KSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG5hbWVzKFgpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VwID0gIioiLCBjb2xsYXBzZSA9ICIgKyAiKSwgIlxuIikNCg0KIyBQcmludCB0aGUgbW9kZWwgZXF1YXRpb24NCmNhdChtb2RlbF9lcXVhdGlvbikNCmBgYA0KDQoNClRoZSByZXN1bHRpbmcgUmlkZ2UgcmVncmVzc2lvbiBlcXVhdGlvbiBpcyBnaXZlbiBieQ0KDQpgYGB7ciwgd2FybmluZ3M9RkFMU0V9DQojIEV4dHJhY3QgY29lZmZpY2llbnRzIGZvciB0aGUgYmVzdCBsYW1iZGENCmJlc3RfbGFtYmRhLnJpZGdlIDwtIGN2X3JpZGdlJGxhbWJkYS5taW4NCmNvZWZmaWNpZW50cy5yaWRnZSA8LSBjb2VmKGN2X3JpZGdlLCBzID0gYmVzdF9sYW1iZGEucmlkZ2UpDQoNCiMgRXh0cmFjdCB0aGUgaW50ZXJjZXB0IGFuZCBiZXRhcw0KaW50ZXJjZXB0LnJpZGdlIDwtIGNvZWZmaWNpZW50cy5yaWRnZVsxXQ0KYmV0YXMucmlkZ2UgPC0gY29lZmZpY2llbnRzLnJpZGdlWy0xXQ0KDQojIFJlY29uc3RydWN0IHRoZSBtb2RlbCBlcXVhdGlvbiBhcyBhIHN0cmluZw0KbW9kZWxfZXF1YXRpb25fcmlkZ2UgPC0gcGFzdGUoIk1vZGVsIGVxdWF0aW9uOiB5ID0iLCByb3VuZChpbnRlcmNlcHQucmlkZ2UsIDQpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIrIiwgcGFzdGUocm91bmQoYmV0YXMucmlkZ2UsIDQpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sbmFtZXMoWCksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiKiIsIGNvbGxhcHNlID0gIiArICIpLCAiXG4iKQ0KDQojIFByaW50IHRoZSBtb2RlbCBlcXVhdGlvbg0KY2F0KG1vZGVsX2VxdWF0aW9uX3JpZGdlKQ0KDQpgYGANCg0KDQpUaGUgcmVzdWx0aW5nIEVsYXN0aWNOZXQgcmVncmVzc2lvbiBlcXVhdGlvbiBpcyBnaXZlbiBieQ0KDQpgYGB7ciwgd2FybmluZ3M9RkFMU0V9DQojIyBFbGFzdGljIE5ldA0KIyBFeHRyYWN0IGNvZWZmaWNpZW50cyBmb3IgdGhlIGJlc3QgbGFtYmRhDQpiZXN0X2xhbWJkYS5uZXQgPC0gY3ZfZWxhc3RpY19uZXQkbGFtYmRhLm1pbg0KY29lZmZpY2llbnRzLm5ldCA8LSBjb2VmKGN2X2VsYXN0aWNfbmV0LCBzID0gYmVzdF9sYW1iZGEubmV0KQ0KDQojIEV4dHJhY3QgdGhlIGludGVyY2VwdCBhbmQgYmV0YXMNCmludGVyY2VwdC5uZXQgPC0gY29lZmZpY2llbnRzLm5ldFsxXQ0KYmV0YXMubmV0IDwtIGNvZWZmaWNpZW50cy5uZXRbLTFdDQoNCiMgUmVjb25zdHJ1Y3QgdGhlIG1vZGVsIGVxdWF0aW9uIGFzIGEgc3RyaW5nDQptb2RlbF9lcXVhdGlvbl9uZXQgPC0gcGFzdGUoIk1vZGVsIGVxdWF0aW9uOiB5ID0iLCByb3VuZChpbnRlcmNlcHQubmV0LCA0KSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIisiLCBwYXN0ZShyb3VuZChiZXRhcy5uZXQsIDQpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG5hbWVzKFgpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICIqIiwgY29sbGFwc2UgPSAiICsgIiksICJcbiIpDQoNCiMgUHJpbnQgdGhlIG1vZGVsIGVxdWF0aW9uDQpjYXQobW9kZWxfZXF1YXRpb25fbmV0KQ0KDQpgYGANCg0KDQpCYXNlZCBvbiB0aGUgUk1TRSAoUm9vdCBNZWFuIFNxdWFyZWQgRXJyb3IpIHZhbHVlcyBmcm9tIHRoZSBjcm9zcy12YWxpZGF0aW9uIHJlc3VsdHMsIHRoZSBtb2RlbCB3aXRoIHRoZSBsb3dlc3QgUk1TRSB2YWx1ZSBpbmRpY2F0ZXMgdGhlIGJlc3QgcHJlZGljdGl2ZSBwZXJmb3JtYW5jZSBmb3IgdGhlIGdsdWNvc2UgdmFyaWFibGUgaW4gdGhlIGhlYXJ0c19maW5hbCBkYXRhc2V0LiBJbiB0aGlzIGNhc2UsIHdpdGggdGhlIFJNU0Ugb2YgMjIuMjEsIHRoZSBiZXN0IG1vZGVsIHRvIHVzZSB0aGUgUmlkZ2UgUmVncmVzc2lvbi4NCg0KDQoNCiMjIDYuUmVndWxhcml6ZWQgTG9naXN0aWNhbCBSZWdyZXNzaW9uDQoNClJlZ3VsYXJpemVkIGxvZ2lzdGljIHJlZ3Jlc3Npb24gaXMgYSBwb3dlcmZ1bCBzdGF0aXN0aWNhbCB0ZWNobmlxdWUgdXNlZCBmb3IgYmluYXJ5IGNsYXNzaWZpY2F0aW9uIHRhc2tzLCB3aGVyZSB0aGUgZ29hbCBpcyB0byBwcmVkaWN0IHRoZSBwcm9iYWJpbGl0eSBvZiBhIGJpbmFyeSBvdXRjb21lIChIYXZpbmcgY29yb25hcnkgaGVhcnQgRGlzZWFzZSkuIFVubGlrZSB0cmFkaXRpb25hbCBsb2dpc3RpYyByZWdyZXNzaW9uLCByZWd1bGFyaXplZCBsb2dpc3RpYyByZWdyZXNzaW9uIGluY29ycG9yYXRlcyBwZW5hbHR5IHRlcm1zIChMMSBvciBMMiByZWd1bGFyaXphdGlvbikgdG8gY29udHJvbCB0aGUgY29tcGxleGl0eSBvZiB0aGUgbW9kZWwsIHByZXZlbnRpbmcgb3ZlcmZpdHRpbmcgYW5kIGltcHJvdmluZyBnZW5lcmFsaXphdGlvbiB0byB1bnNlZW4gZGF0YS4NCg0KQmVsb3cgaXMgdGhlIGNvZGUgZm9yIHRoZSBmdWxsIGxvZ2lzdGljYWwgbW9kZWwNCg0KYGBge3IsIGZpZy5hbGlnbj0nY2VudGVyJywgd2FybmluZ3M9RkFMU0V9DQpsaWJyYXJ5KGdsbW5ldCkNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KHBhbmRlcikNCg0KIyBEZWZpbmUgdGhlIGZlYXR1cmVzIChwcmVkaWN0b3JzKSBhbmQgdGFyZ2V0DQpYIDwtIGFzLm1hdHJpeChoZWFydHNfZmluYWxbLCAtd2hpY2gobmFtZXMoaGVhcnRzX2ZpbmFsKSA9PSAiZ2x1Y29zZSIpXSkNCiMgUHJlZGljdG9ycw0KeSA8LSBoZWFydHNfZmluYWwkVGVuWWVhckNIRCAgIyBCaW5hcnkgdGFyZ2V0IHZhcmlhYmxlIChUZW5ZZWFyQ0hEKQ0KDQojIEVuc3VyZSB0YXJnZXQgdmFyaWFibGUgaXMgYmluYXJ5ICgwIG9yIDEpDQp5IDwtIGlmZWxzZSh5ID09IDEsIDEsIDApDQoNCiMgU3BsaXQgdGhlIGRhdGEgaW50byB0cmFpbmluZyBhbmQgdGVzdGluZyBzZXRzDQpzZXQuc2VlZCgxMjMpDQp0cmFpbkluZGV4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oeSwgcCA9IDAuOCwgbGlzdCA9IEZBTFNFKQ0KWF90cmFpbiA8LSBYW3RyYWluSW5kZXgsIF0NClhfdGVzdCA8LSBYWy10cmFpbkluZGV4LCBdDQp5X3RyYWluIDwtIHlbdHJhaW5JbmRleF0NCnlfdGVzdCA8LSB5Wy10cmFpbkluZGV4XQ0KDQojIyMjIyMjIyMjIyMjIyMjIyMjIw0KIyBGaXQgTEFTU08gbW9kZWwgKEwxIFJlZ3VsYXJpemF0aW9uKQ0KIyMjIyMjIyMjIyMjIyMjIyMjIyMNCmxhc3NvX21vZGVsIDwtIGdsbW5ldChYX3RyYWluLCB5X3RyYWluLCBmYW1pbHkgPSAiYmlub21pYWwiLCBhbHBoYSA9IDEpDQoNCiMgQ3Jvc3MtdmFsaWRhdGlvbiB0byBmaW5kIHRoZSBvcHRpbWFsIGxhbWJkYSBmb3IgTGFzc28NCmN2X2xhc3NvIDwtIGN2LmdsbW5ldChYX3RyYWluLCB5X3RyYWluLCBmYW1pbHkgPSAiYmlub21pYWwiLCBhbHBoYSA9IDEpDQpsYW1iZGFfbGFzc28gPC0gY3ZfbGFzc28kbGFtYmRhLm1pbg0KcHJpbnQobGFtYmRhX2xhc3NvKQ0KDQojIFJlZml0IHRoZSBtb2RlbCB3aXRoIHRoZSBvcHRpbWFsIGxhbWJkYQ0KbGFzc29fbW9kZWxfb3B0IDwtIGdsbW5ldChYX3RyYWluLCB5X3RyYWluLCBmYW1pbHkgPSAiYmlub21pYWwiLCBhbHBoYSA9IDEsIGxhbWJkYSA9IGxhbWJkYV9sYXNzbykNCg0KIyBNYWtlIHByZWRpY3Rpb25zIG9uIHRoZSB0ZXN0IHNldA0KbGFzc29fcHJlZGljdGlvbnMgPC0gcHJlZGljdChsYXNzb19tb2RlbF9vcHQsIHMgPSBsYW1iZGFfbGFzc28sIG5ld3ggPSBYX3Rlc3QsIHR5cGUgPSAicmVzcG9uc2UiKQ0KDQojIENvbnZlcnQgcHJlZGljdGVkIHByb2JhYmlsaXRpZXMgdG8gYmluYXJ5IHByZWRpY3Rpb25zICh0aHJlc2hvbGQgMC41KQ0KbGFzc29fYmluYXJ5X3ByZWRpY3Rpb25zIDwtIGlmZWxzZShsYXNzb19wcmVkaWN0aW9ucyA+IDAuNSwgMSwgMCkNCg0KIyBFdmFsdWF0ZSB0aGUgbW9kZWwgdXNpbmcgY29uZnVzaW9uIG1hdHJpeA0KY29uZnVzaW9uX21hdHJpeF9sYXNzbyA8LSBjb25mdXNpb25NYXRyaXgoZmFjdG9yKGxhc3NvX2JpbmFyeV9wcmVkaWN0aW9ucyksIGZhY3Rvcih5X3Rlc3QpKQ0KcHJpbnQoY29uZnVzaW9uX21hdHJpeF9sYXNzbykNCg0KIyBQcmludCBMYXNzbyBtb2RlbCBjb2VmZmljaWVudHMNCmNvZWYobGFzc29fbW9kZWxfb3B0LCBzID0gbGFtYmRhX2xhc3NvKQ0KDQojIyMjIyMjIyMjIyMjIyMjIyMjIw0KIyBGaXQgUmlkZ2UgbW9kZWwgKEwyIFJlZ3VsYXJpemF0aW9uKQ0KIyMjIyMjIyMjIyMjIyMjIyMjIyMNCnJpZGdlX21vZGVsIDwtIGdsbW5ldChYX3RyYWluLCB5X3RyYWluLCBmYW1pbHkgPSAiYmlub21pYWwiLCBhbHBoYSA9IDApDQoNCiMgQ3Jvc3MtdmFsaWRhdGlvbiB0byBmaW5kIHRoZSBvcHRpbWFsIGxhbWJkYSBmb3IgUmlkZ2UNCmN2X3JpZGdlIDwtIGN2LmdsbW5ldChYX3RyYWluLCB5X3RyYWluLCBmYW1pbHkgPSAiYmlub21pYWwiLCBhbHBoYSA9IDApDQpsYW1iZGFfcmlkZ2UgPC0gY3ZfcmlkZ2UkbGFtYmRhLm1pbg0KcHJpbnQobGFtYmRhX3JpZGdlKQ0KDQojIFJlZml0IHRoZSBtb2RlbCB3aXRoIHRoZSBvcHRpbWFsIGxhbWJkYQ0KcmlkZ2VfbW9kZWxfb3B0IDwtIGdsbW5ldChYX3RyYWluLCB5X3RyYWluLCBmYW1pbHkgPSAiYmlub21pYWwiLCBhbHBoYSA9IDAsIGxhbWJkYSA9IGxhbWJkYV9yaWRnZSkNCg0KIyBNYWtlIHByZWRpY3Rpb25zIG9uIHRoZSB0ZXN0IHNldA0KcmlkZ2VfcHJlZGljdGlvbnMgPC0gcHJlZGljdChyaWRnZV9tb2RlbF9vcHQsIHMgPSBsYW1iZGFfcmlkZ2UsIG5ld3ggPSBYX3Rlc3QsIHR5cGUgPSAicmVzcG9uc2UiKQ0KDQojIENvbnZlcnQgcHJlZGljdGVkIHByb2JhYmlsaXRpZXMgdG8gYmluYXJ5IHByZWRpY3Rpb25zICh0aHJlc2hvbGQgMC41KQ0KcmlkZ2VfYmluYXJ5X3ByZWRpY3Rpb25zIDwtIGlmZWxzZShyaWRnZV9wcmVkaWN0aW9ucyA+IDAuNSwgMSwgMCkNCg0KIyBFdmFsdWF0ZSB0aGUgbW9kZWwgdXNpbmcgY29uZnVzaW9uIG1hdHJpeA0KY29uZnVzaW9uX21hdHJpeF9yaWRnZSA8LSBjb25mdXNpb25NYXRyaXgoZmFjdG9yKHJpZGdlX2JpbmFyeV9wcmVkaWN0aW9ucyksIGZhY3Rvcih5X3Rlc3QpKQ0KcHJpbnQoY29uZnVzaW9uX21hdHJpeF9yaWRnZSkNCg0KIyBQcmludCBSaWRnZSBtb2RlbCBjb2VmZmljaWVudHMNCmNvZWYocmlkZ2VfbW9kZWxfb3B0LCBzID0gbGFtYmRhX3JpZGdlKQ0KDQojIyMjIyMjIyMjIyMjIyMjIyMjIw0KIyBGaXQgRWxhc3RpYyBOZXQgbW9kZWwgKEwxIGFuZCBMMiBSZWd1bGFyaXphdGlvbikNCiMjIyMjIyMjIyMjIyMjIyMjIyMjDQplbGFzdGljX21vZGVsIDwtIGdsbW5ldChYX3RyYWluLCB5X3RyYWluLCBmYW1pbHkgPSAiYmlub21pYWwiLCBhbHBoYSA9IDAuNSkNCg0KIyBDcm9zcy12YWxpZGF0aW9uIHRvIGZpbmQgdGhlIG9wdGltYWwgbGFtYmRhIGZvciBFbGFzdGljIE5ldA0KY3ZfZWxhc3RpYyA8LSBjdi5nbG1uZXQoWF90cmFpbiwgeV90cmFpbiwgZmFtaWx5ID0gImJpbm9taWFsIiwgYWxwaGEgPSAwLjUpDQpsYW1iZGFfZWxhc3RpYyA8LSBjdl9lbGFzdGljJGxhbWJkYS5taW4NCnByaW50KGxhbWJkYV9lbGFzdGljKQ0KDQojIFJlZml0IHRoZSBtb2RlbCB3aXRoIHRoZSBvcHRpbWFsIGxhbWJkYQ0KZWxhc3RpY19tb2RlbF9vcHQgPC0gZ2xtbmV0KFhfdHJhaW4sIHlfdHJhaW4sIGZhbWlseSA9ICJiaW5vbWlhbCIsIGFscGhhID0gMC41LCBsYW1iZGEgPSBsYW1iZGFfZWxhc3RpYykNCg0KIyBNYWtlIHByZWRpY3Rpb25zIG9uIHRoZSB0ZXN0IHNldA0KZWxhc3RpY19wcmVkaWN0aW9ucyA8LSBwcmVkaWN0KGVsYXN0aWNfbW9kZWxfb3B0LCBzID0gbGFtYmRhX2VsYXN0aWMsIG5ld3ggPSBYX3Rlc3QsIHR5cGUgPSAicmVzcG9uc2UiKQ0KDQojIENvbnZlcnQgcHJlZGljdGVkIHByb2JhYmlsaXRpZXMgdG8gYmluYXJ5IHByZWRpY3Rpb25zICh0aHJlc2hvbGQgMC41KQ0KZWxhc3RpY19iaW5hcnlfcHJlZGljdGlvbnMgPC0gaWZlbHNlKGVsYXN0aWNfcHJlZGljdGlvbnMgPiAwLjUsIDEsIDApDQoNCiMgRXZhbHVhdGUgdGhlIG1vZGVsIHVzaW5nIGNvbmZ1c2lvbiBtYXRyaXgNCmNvbmZ1c2lvbl9tYXRyaXhfZWxhc3RpYyA8LSBjb25mdXNpb25NYXRyaXgoZmFjdG9yKGVsYXN0aWNfYmluYXJ5X3ByZWRpY3Rpb25zKSwgZmFjdG9yKHlfdGVzdCkpDQpwcmludChjb25mdXNpb25fbWF0cml4X2VsYXN0aWMpDQoNCiMgUHJpbnQgRWxhc3RpYyBOZXQgbW9kZWwgY29lZmZpY2llbnRzDQpjb2VmKGVsYXN0aWNfbW9kZWxfb3B0LCBzID0gbGFtYmRhX2VsYXN0aWMpDQpgYGANCg0KDQojIyBPcHRpbWFsIEN1dG9mZiBQcm9iYWJpbGl0eQ0KDQpJbiBiaW5hcnkgY2xhc3NpZmljYXRpb24sIHRoZSBtb2RlbCBvdXRwdXRzIHByb2JhYmlsaXRpZXMgcmF0aGVyIHRoYW4gZGlyZWN0IGNsYXNzIGxhYmVscy4gVGhlc2UgcHJvYmFiaWxpdGllcyByZXByZXNlbnQgdGhlIGxpa2VsaWhvb2Qgb2YgdGhlIGV2ZW50IChpbiB0aGlzIGNhc2UsIHRoZSBvY2N1cnJlbmNlIG9mIGhlYXJ0IGRpc2Vhc2UgaW4gMTAgeWVhcnMpLiBUeXBpY2FsbHksIGEgdGhyZXNob2xkIChjdXRvZmYpIGlzIGFwcGxpZWQgdG8gY29udmVydCB0aGVzZSBjb250aW51b3VzIHByb2JhYmlsaXRpZXMgaW50byBiaW5hcnkgb3V0Y29tZXMgKGUuZy4sIDAgb3IgMSkuDQoNClRoZSBvcHRpbWFsIGN1dG9mZiBwcm9iYWJpbGl0eSBpcyBkZXRlcm1pbmVkIGJ5IG1heGltaXppbmcgdGhlIG1vZGVsJ3MgcGVyZm9ybWFuY2UgYmFzZWQgb24gdGhlIHNwZWNpZmljIGdvYWxzIG9mIHRoZSBhbmFseXNpcy4gIEJ5IGFkanVzdGluZyB0aGUgY3V0b2ZmIHRocmVzaG9sZCwgd2UgY2FuIGZpbmQgYSBiYWxhbmNlIHRoYXQgbWF4aW1pemVzIG1vZGVsIHBlcmZvcm1hbmNlLg0KDQpgYGB7ciwgd2FybmluZ3M9RkFMU0V9DQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KIyBQcmVkaWN0IG9uIHRoZSB0ZXN0IHNldDogdHlwZSA9ICJyZXNwb25zZSIgZ2l2ZXMgcHJvYmFiaWxpdGllcw0KcHJlZGljdF9sYXNzbyA8LSBwcmVkaWN0KGxhc3NvX21vZGVsX29wdCwgbmV3eCA9IFhfdGVzdCwgdHlwZSA9ICJyZXNwb25zZSIpDQpwcmVkaWN0X3JpZGdlIDwtIHByZWRpY3QocmlkZ2VfbW9kZWxfb3B0LCBuZXd4ID0gWF90ZXN0LCB0eXBlID0gInJlc3BvbnNlIikNCnByZWRpY3RfZWxhc3RpYyA8LSBwcmVkaWN0KGVsYXN0aWNfbW9kZWxfb3B0LCBuZXd4ID0gWF90ZXN0LCB0eXBlID0gInJlc3BvbnNlIikNCg0KIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KIyMgT3B0aW1hbCBjdXRvZmYgcHJvYmFiaWxpdHkgZGV0ZXJtaW5hdGlvbg0Kc2VxLmN1dCA8LSBzZXEoMCwgMSwgbGVuZ3RoID0gNTApICAjIFNlcXVlbmNlIG9mIGN1dG9mZiB2YWx1ZXMNCmFjYy5sYXNzbyA8LSBOVUxMDQphY2MucmlkZ2UgPC0gTlVMTA0KYWNjLmVsYXN0aWMgPC0gTlVMTA0KDQpmb3IgKGkgaW4gMTpsZW5ndGgoc2VxLmN1dCkpIHsNCiAgIyBDb252ZXJ0IHByb2JhYmlsaXRpZXMgdG8gYmluYXJ5IHByZWRpY3Rpb25zIGJhc2VkIG9uIHRoZSBjdXRvZmYgdmFsdWUNCiAgcHJlZHkubGFzc28gPC0gaWZlbHNlKHByZWRpY3RfbGFzc28gPiBzZXEuY3V0W2ldLCAxLCAwKQ0KICBwcmVkeS5yaWRnZSA8LSBpZmVsc2UocHJlZGljdF9yaWRnZSA+IHNlcS5jdXRbaV0sIDEsIDApDQogIHByZWR5LmVsYXN0aWMgPC0gaWZlbHNlKHByZWRpY3RfZWxhc3RpYyA+IHNlcS5jdXRbaV0sIDEsIDApDQogIA0KICAjIENhbGN1bGF0ZSBhY2N1cmFjeSBmb3IgZWFjaCBtb2RlbA0KICBhY2MubGFzc29baV0gPC0gbWVhbih5X3Rlc3QgPT0gcHJlZHkubGFzc28pDQogIGFjYy5yaWRnZVtpXSA8LSBtZWFuKHlfdGVzdCA9PSBwcmVkeS5yaWRnZSkNCiAgYWNjLmVsYXN0aWNbaV0gPC0gbWVhbih5X3Rlc3QgPT0gcHJlZHkuZWxhc3RpYykNCn0NCg0KIyMgT3B0aW1hbCBjdXQtb2ZmOiBhdmVyYWdlIGN1dG9mZiBpZiBtdWx0aXBsZSBjdXRvZmZzIGdpdmUgbWF4IGFjY3VyYWN5DQpvcHQuY3V0Lmxhc3NvIDwtIG1lYW4oc2VxLmN1dFt3aGljaChhY2MubGFzc28gPT0gbWF4KGFjYy5sYXNzbykpXSkNCm9wdC5jdXQucmlkZ2UgPC0gbWVhbihzZXEuY3V0W3doaWNoKGFjYy5yaWRnZSA9PSBtYXgoYWNjLnJpZGdlKSldKQ0Kb3B0LmN1dC5lbGFzdGljIDwtIG1lYW4oc2VxLmN1dFt3aGljaChhY2MuZWxhc3RpYyA9PSBtYXgoYWNjLmVsYXN0aWMpKV0pDQoNCiMjIERhdGEgZnJhbWUgdG8gc3RvcmUgYWNjdXJhY2llcyBhbmQgY3V0b2ZmIHByb2JhYmlsaXRpZXMNCmFjYy5kYXRhIDwtIGRhdGEuZnJhbWUoDQogIHByb2IgPSByZXAoc2VxLmN1dCwgMyksDQogIGFjYyA9IGMoYWNjLmxhc3NvLCBhY2MucmlkZ2UsIGFjYy5lbGFzdGljKSwNCiAgZ3JvdXAgPSBjKHJlcCgibGFzc28iLCA1MCksIHJlcCgicmlkZ2UiLCA1MCksIHJlcCgiZWxhc3RpYyIsIDUwKSkNCikNCg0KIyBQbG90IGFjY3VyYWN5IHZzLiBjdXRvZmYgZm9yIGVhY2ggbW9kZWwNCmxpYnJhcnkoZ2dwbG90MikNCmdncGxvdChhY2MuZGF0YSwgYWVzKHggPSBwcm9iLCB5ID0gYWNjLCBjb2xvciA9IGdyb3VwKSkgKw0KICBnZW9tX2xpbmUoKSArDQogIGxhYnModGl0bGUgPSAiQWNjdXJhY3kgdnMuIEN1dG9mZiBmb3IgTEFTU08sIFJpZGdlLCBhbmQgRWxhc3RpYyBOZXQiLA0KICAgICAgIHggPSAiQ3V0b2ZmIFByb2JhYmlsaXR5IiwgeSA9ICJBY2N1cmFjeSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCiMgUHJpbnQgdGhlIG9wdGltYWwgY3V0b2ZmIHByb2JhYmlsaXRpZXMNCmNhdCgiT3B0aW1hbCBjdXRvZmYgZm9yIExBU1NPOiAiLCBvcHQuY3V0Lmxhc3NvLCAiXG4iKQ0KY2F0KCJPcHRpbWFsIGN1dG9mZiBmb3IgUmlkZ2U6ICIsIG9wdC5jdXQucmlkZ2UsICJcbiIpDQpjYXQoIk9wdGltYWwgY3V0b2ZmIGZvciBFbGFzdGljIE5ldDogIiwgb3B0LmN1dC5lbGFzdGljLCAiXG4iKQ0KYGBgDQpIZXJlIHdlIGNhbiBzZWUgdGhhdCBlYWNoIG1vZGVsIGhhcyBhIGRpZmZlcmVudCBvcHRpbWFsIGN1dG9mZi4gDQpgYGB7ciwgd2FybmluZ3M9RkFMU0V9DQoNCmdnLmFjYyA8LSBnZ3Bsb3QoZGF0YSA9IGFjYy5kYXRhLCBhZXMoeD1wcm9iLCB5ID0gYWNjLCBjb2xvciA9IGdyb3VwKSkgKw0KICBnZW9tX2xpbmUoKSArDQogIGFubm90YXRlKCJ0ZXh0IiwgeCA9IDAuNiwgeSA9IDAuNDUsIA0KICAgICAgICAgICBsYWJlbCA9IHBhc3RlKCJMQVNTTyBjdXRvZmY6ICIsIHJvdW5kKG9wdC5jdXQubGFzc28sNSksICJBY2N1cmFjeTogIiwgcm91bmQobWF4KGFjYy5sYXNzbyksNSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICJcblJpZGdlIGN1dG9mZjogIiwgcm91bmQob3B0LmN1dC5yaWRnZSw1KSwgIkFjY3VyYWN5OiAiLCByb3VuZChtYXgoYWNjLnJpZGdlKSw1KSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIlxuRWxhc3RpYyBjdXRvZmY6ICIsIHJvdW5kKG9wdC5jdXQuZWxhc3RpYyw1KSwgIkFjY3VyYWN5OiAiLCByb3VuZChtYXgoYWNjLmVsYXN0aWMpLDUpKSwgDQogICAgICAgICAgIHNpemUgPSAzLCANCiAgICAgICAgICAgY29sb3IgPSAibmF2eSIpICsNCiAgZ2d0aXRsZSgiQ3V0LW9mZiBQcm9iYWJpbGl0eSB2cyBBY2N1cmFjeSIpICsNCiAgbGFicyh4ID0gImN1dC1vZmYgUHJvYmFiaWxpdHkiLCANCiAgICAgICB5ID0gImFjY3VyYWN5IiwgY29sb3IgPSAiR3JvdXAiKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQ0KDQojIw0KZ2dwbG90bHkoZ2cuYWNjKQ0KYGBgDQpOb3cgdXNpbmcgdGhlc2UgY3V0b2ZmIHdlIGNhbiBwcmVkaWN0IGFuZCBhc3NpZ24gbGFiZWxzLg0KDQpgYGB7ciwgd2FybmluZ3M9RkFMU0V9DQoNCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KIyMgdXNpbmcgdGhlIG9wdGltYWwgY3V0b2ZmIHByb2JhYmlsaXR5IHRvIHByZWRpY3QgbGFiZWxzDQojIyANCnByZWQubGFiLmxhc3NvIDwtIGlmZWxzZShwcmVkaWN0X2xhc3NvID5vcHQuY3V0Lmxhc3NvLCAxLCAwKQ0KcHJlZC5sYWIucmlkZ2U8LSBpZmVsc2UocHJlZGljdF9yaWRnZSA+b3B0LmN1dC5yaWRnZSwgMSwgMCkNCnByZWQubGFiLmVsYXN0aWM8LSBpZmVsc2UocHJlZGljdF9lbGFzdGljID5vcHQuY3V0LmVsYXN0aWMsIDEsIDApDQoNCg0KIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQojIENvbnZlcnQgcHJlZGljdGlvbnMgdG8gZmFjdG9ycw0KcHJlZC5sYWIubGFzc28uZmN0IDwtIGFzLmZhY3RvcihwcmVkLmxhYi5sYXNzbykNCnByZWQubGFiLnJpZGdlLmZjdCA8LSBhcy5mYWN0b3IocHJlZC5sYWIucmlkZ2UpDQpwcmVkLmxhYi5lbGFzdGljLmZjdCA8LSBhcy5mYWN0b3IocHJlZC5sYWIuZWxhc3RpYykNCg0KIyBDb252ZXJ0IGFjdHVhbCB2YWx1ZXMgdG8gZmFjdG9ycw0KeV90ZXN0IDwtIGFzLmZhY3Rvcih5X3Rlc3QpDQoNCiMgQ29uZnVzaW9uIE1hdHJpeCBhbmQgTWV0cmljcw0KY29uZnVzaW9uLmxhc3NvIDwtIGNvbmZ1c2lvbk1hdHJpeChwcmVkLmxhYi5sYXNzby5mY3QsIHlfdGVzdCkNCmNvbmZ1c2lvbi5yaWRnZTwtIGNvbmZ1c2lvbk1hdHJpeChwcmVkLmxhYi5yaWRnZS5mY3QsIHlfdGVzdCkNCmNvbmZ1c2lvbi5lbGFzdGljIDwtIGNvbmZ1c2lvbk1hdHJpeChwcmVkLmxhYi5lbGFzdGljLmZjdCwgeV90ZXN0KQ0KDQojIyBDb21tb25seSB1c2VkIHBlcmZvcm1hbmNlIG1lYXN1cmVkDQpQZXJmTWVhc3VyZXMgPC0gY2JpbmQobGFzc28gPSBjb25mdXNpb24ubGFzc28kYnlDbGFzcywgDQogICAgICAgICAgICAgICAgICAgICByaWRnZSA9IGNvbmZ1c2lvbi5yaWRnZSRieUNsYXNzLCANCiAgICAgICAgICAgICAgICAgICAgIGVsYXN0aWMgPSBjb25mdXNpb24uZWxhc3RpYyRieUNsYXNzKQ0KcGFuZGVyKFBlcmZNZWFzdXJlcykNCg0KYGBgDQojIyMgUk9DIEFuYWx5c2lzDQoNClRoZSBST0MgY3VydmUgaXMgYSBncmFwaGljYWwgcmVwcmVzZW50YXRpb24gdGhhdCBpbGx1c3RyYXRlcyB0aGUgdHJhZGUtb2ZmIGJldHdlZW4gc2Vuc2l0aXZpdHkgKHRydWUgcG9zaXRpdmUgcmF0ZSkgYW5kIDEtc3BlY2lmaWNpdHkgKGZhbHNlIHBvc2l0aXZlIHJhdGUpIGFjcm9zcyBkaWZmZXJlbnQgdGhyZXNob2xkIHZhbHVlcy4gIEEgbW9kZWwgdGhhdCBwZXJmb3JtcyB3ZWxsIHdpbGwgaGF2ZSBhIFJPQyBjdXJ2ZSB0aGF0IHJpc2VzIHNoYXJwbHkgdG93YXJkcyB0aGUgdG9wLWxlZnQgY29ybmVyLCBpbmRpY2F0aW5nIGhpZ2ggc2Vuc2l0aXZpdHkgYW5kIGxvdyBmYWxzZSBwb3NpdGl2ZSByYXRlLiBUaGUgQXJlYSBVbmRlciB0aGUgQ3VydmUgKEFVQykgaXMgYW5vdGhlciBpbXBvcnRhbnQgbWV0cmljIGRlcml2ZWQgZnJvbSB0aGUgUk9DIGFuYWx5c2lzLCB3aGljaCBxdWFudGlmaWVzIHRoZSBvdmVyYWxsIGFiaWxpdHkgb2YgdGhlIG1vZGVsIHRvIGRpc2NyaW1pbmF0ZSBiZXR3ZWVuIHRoZSBwb3NpdGl2ZSBhbmQgbmVnYXRpdmUgY2xhc3Nlcy4gQW4gQVVDIHZhbHVlIGNsb3NlciB0byAxIGluZGljYXRlcyBleGNlbGxlbnQgbW9kZWwgcGVyZm9ybWFuY2UsIHdoaWxlIGEgdmFsdWUgY2xvc2VyIHRvIDAuNSBzdWdnZXN0cyB0aGUgbW9kZWwgaGFzIG5vIGRpc2NyaW1pbmF0b3J5IHBvd2VyLg0KYGBge3IsIGZpZy5hbGlnbj0gJ2NlbnRlcicsIGZpZy5jYXA9IjUtZm9sZCBDViBwZXJmb3JtYW5jZSBwbG90Iiwgd2FybmluZ3M9RkFMU0V9DQpsaWJyYXJ5KHBST0MpDQoNCiMgUHJlZGljdGVkIHByb2JhYmlsaXRpZXMgZm9yIGVhY2ggbW9kZWwNCnByb2JfbGFzc28gPC0gcHJlZGljdChsYXNzb19tb2RlbF9vcHQsIG5ld3ggPSBYX3Rlc3QsIHR5cGUgPSAicmVzcG9uc2UiKQ0KcHJvYl9yaWRnZSA8LSBwcmVkaWN0KHJpZGdlX21vZGVsX29wdCwgbmV3eCA9IFhfdGVzdCwgdHlwZSA9ICJyZXNwb25zZSIpDQpwcm9iX2VsYXN0aWMgPC0gcHJlZGljdChlbGFzdGljX21vZGVsX29wdCwgbmV3eCA9IFhfdGVzdCwgdHlwZSA9ICJyZXNwb25zZSIpDQoNCiMgQ29tcHV0ZSBST0MgY3VydmVzDQpyb2NfbGFzc28gPC0gcm9jKHlfdGVzdCwgcHJvYl9sYXNzbykNCnJvY19yaWRnZSA8LSByb2MoeV90ZXN0LCBwcm9iX3JpZGdlKQ0Kcm9jX2VsYXN0aWMgPC0gcm9jKHlfdGVzdCwgcHJvYl9lbGFzdGljKQ0KDQojIENvbXB1dGUgQVVDIHZhbHVlcw0KYXVjX2xhc3NvIDwtIGF1Yyhyb2NfbGFzc28pDQphdWNfcmlkZ2UgPC0gYXVjKHJvY19yaWRnZSkNCmF1Y19lbGFzdGljIDwtIGF1Yyhyb2NfZWxhc3RpYykNCg0KIyMgRXh0cmFjdCBzZW5zaXRpdml0eSBhbmQgc3BlY2lmaWNpdHkgdmFsdWVzDQpzZW4ubGFzc28gPC0gcm9jX2xhc3NvJHNlbnNpdGl2aXRpZXMNCnNwZS5sYXNzbyA8LSByb2NfbGFzc28kc3BlY2lmaWNpdGllcw0KDQpzZW4ucmlkZ2UgPC0gcm9jX3JpZGdlJHNlbnNpdGl2aXRpZXMNCnNwZS5yaWRnZSA8LSByb2NfcmlkZ2Ukc3BlY2lmaWNpdGllcw0KDQpzZW4uZWxhc3RpYyA8LSByb2NfZWxhc3RpYyRzZW5zaXRpdml0aWVzDQpzcGUuZWxhc3RpYyA8LSByb2NfZWxhc3RpYyRzcGVjaWZpY2l0aWVzDQoNCiMgUGxvdCB0aGUgUk9DIGN1cnZlcw0KcGxvdCgxIC0gc3BlLmxhc3NvLCBzZW4ubGFzc28sIA0KICAgICB0eXBlID0gImwiLCBjb2wgPSAiZ3JlZW4iLCANCiAgICAgeGxpbSA9IGMoMCwxKSwNCiAgICAgeGxhYiA9ICIxIC0gU3BlY2lmaWNpdHkiLA0KICAgICB5bGFiID0gIlNlbnNpdGl2aXR5IiwNCiAgICAgbWFpbiA9ICJST0MgQ3VydmVzIGZvciBMQVNTTywgUmlkZ2UsIGFuZCBFbGFzdGljIE5ldCIpDQoNCmxpbmVzKDEgLSBzcGUucmlkZ2UsIHNlbi5yaWRnZSwgY29sID0gIm9yYW5nZSIpDQpsaW5lcygxIC0gc3BlLmVsYXN0aWMsIHNlbi5lbGFzdGljLCBjb2wgPSAicHVycGxlIikNCmFibGluZSgwLCAxLCB0eXBlID0gImwiLCBsdHkgPSAyLCBjb2wgPSAic3RlZWxibHVlIiwgbHdkID0gMSkgICMgRGlhZ29uYWwgbGluZQ0KDQojIEFkZCBsZWdlbmQNCmxlZ2VuZCgiYm90dG9tcmlnaHQiLCBsZWdlbmQgPSBjKHBhc3RlKCJMQVNTTyAoQVVDID0iLCByb3VuZChhdWNfbGFzc28sIDMpLCAiKSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoIlJpZGdlIChBVUMgPSIsIHJvdW5kKGF1Y19yaWRnZSwgMyksICIpIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZSgiRWxhc3RpYyBOZXQgKEFVQyA9Iiwgcm91bmQoYXVjX2VsYXN0aWMsIDMpLCAiKSIpKSwNCiAgICAgICBjb2wgPSBjKCJncmVlbiIsICJvcmFuZ2UiLCAicHVycGxlIiksIGx0eSA9IDEsIGNleCA9IDAuOCwgYnR5ID0gIm4iKQ0KDQoNCmNvbG5hbWVzKGhlYXJ0c19maW5hbCkNCmBgYA0KVGhlIGFib3ZlIGZpZ3VyZSBpbmRpY2F0ZXMgdGhhdCB0aGUgb3B0aW1hbCBjdXQtb2ZmIHByb2JhYmlsaXR5IHRoYXQgeWllbGRzIHRoZSBiZXN0IGFjY3VyYWN5IGlzIDAuNDguDQoNCg0KDQoNCiMjIDcuTGluZWFyIGFuZCBSQkYgU1ZNcw0KDQpXZSB3aWxsIGZpdCBTdXBwb3J0IFZlY3RvciBSZWdyZXNzaW9uIChTVlIpIG1vZGVscyB3aXRoIGJvdGggbGluZWFyIGFuZCByYWRpYWwgYmFzaXMgZnVuY3Rpb24gKFJCRikga2VybmVscywgYXMgd2VsbCBhcyBhbiBvcmRpbmFyeSBsZWFzdCBzcXVhcmVzIChPTFMpIHJlZ3Jlc3Npb24gbW9kZWwgKHdpdGggc3RlcC13aXNlIHZhcmlhYmxlIHNlbGVjdGlvbikuIFRoZSBwZXJmb3JtYW5jZSBvZiB0aGVzZSB0aHJlZSByZWdyZXNzaW9uIG1vZGVscyB3aWxsIGJlIGV2YWx1YXRlZCB1c2luZyBtZWFuIHNxdWFyZWQgZXJyb3IgKE1TRSkgYW5kIG1lYW4gYWJzb2x1dGUgZXJyb3IgKE1BRSkuIFRoaXMgd2lsbCBiZSB1c2VkIHRvIHByZWRpY3QgZ2x1Y29zZSBsZXZlbHMgaW4gcGF0aWVudHMuDQoNCmBgYHtyLCB3YXJuaW5ncz1GQUxTRX0NCiMgTG9hZCByZXF1aXJlZCBsaWJyYXJpZXMNCmxpYnJhcnkoZTEwNzEpDQoNCiMgQXNzdW1pbmcgaGVhcnRzX2ZpbmFsIGlzIHlvdXIgZGF0YXNldA0KIyBDaGVjayBmb3IgbWlzc2luZyB2YWx1ZXMgYW5kIHJlbW92ZSByb3dzIHdpdGggTkEgdmFsdWVzDQpYIDwtIGhlYXJ0c19maW5hbFssIC13aGljaChuYW1lcyhoZWFydHNfZmluYWwpID09ICJnbHVjb3NlIildICAjIEFsbCBmZWF0dXJlcyBleGNlcHQgJ2dsdWNvc2UnDQp5IDwtIGhlYXJ0c19maW5hbCRnbHVjb3NlICAjIFRhcmdldCB2YXJpYWJsZSAnZ2x1Y29zZScNCg0KIyBSZW1vdmUgcm93cyB3aXRoIE5BIHZhbHVlcyBmcm9tIGJvdGggcHJlZGljdG9ycyAoWCkgYW5kIHRhcmdldCAoeSkNCmNvbXBsZXRlX2RhdGEgPC0gY29tcGxldGUuY2FzZXMoWCwgeSkNClggPC0gWFtjb21wbGV0ZV9kYXRhLCBdDQp5IDwtIHlbY29tcGxldGVfZGF0YV0NCg0KIyBFbnN1cmUgYWxsIHByZWRpY3RvciBjb2x1bW5zIGFyZSBudW1lcmljDQpYW10gPC0gbGFwcGx5KFgsIGFzLm51bWVyaWMpICAjIENvbnZlcnQgYWxsIGNvbHVtbnMgaW4gWCB0byBudW1lcmljDQoNCiMgU2NhbGUgdGhlIHByZWRpY3RvcnMgKG9wdGlvbmFsIGJ1dCBoZWxwcyBpbiBTVk1zKQ0KWCA8LSBzY2FsZShYKQ0KDQojIFNwbGl0IHRoZSBkYXRhIGludG8gdHJhaW5pbmcgYW5kIHRlc3Qgc2V0cyAoODAtMjAgc3BsaXQpDQpzZXQuc2VlZCgxMjMpICAjIEZvciByZXByb2R1Y2liaWxpdHkNCnRyYWluLmluZGV4IDwtIHNhbXBsZSgxOm5yb3coWCksIDAuOCAqIG5yb3coWCkpDQpYLnRyYWluIDwtIFhbdHJhaW4uaW5kZXgsIF0NCnkudHJhaW4gPC0geVt0cmFpbi5pbmRleF0NClgudGVzdCA8LSBYWy10cmFpbi5pbmRleCwgXQ0KeS50ZXN0IDwtIHlbLXRyYWluLmluZGV4XQ0KDQojIFNldCB1cCB0aGUgZ3JpZCBmb3IgaHlwZXJwYXJhbWV0ZXJzIChSQkYga2VybmVsKQ0KdHVuZS5ncmlkIDwtIGV4cGFuZC5ncmlkKA0KICBlcHNpbG9uID0gc2VxKDAuMSwgMC41LCAwLjEpLA0KICBjb3N0ID0gYygxLCAxMCwgMTAwKSwNCiAgZ2FtbWEgPSBjKDAuMDEsIDAuMSwgMSkNCikNCg0KIyBTZXQgdXAgY3Jvc3MtdmFsaWRhdGlvbiBjb250cm9sICg1LWZvbGQgY3Jvc3MtdmFsaWRhdGlvbikNCnR1bmUuY29udHJvbCA8LSB0dW5lLmNvbnRyb2woDQogIGNyb3NzID0gNSwgICMgNS1mb2xkIGNyb3NzLXZhbGlkYXRpb24NCiAgbnJlcGVhdCA9IDEgICMgTnVtYmVyIG9mIHJlcGV0aXRpb25zDQopDQoNCiMgUGVyZm9ybSBncmlkIHNlYXJjaCBmb3IgaHlwZXJwYXJhbWV0ZXIgdHVuaW5nOiBSQkYga2VybmVsDQp0dW5lLlJCRiA8LSB0dW5lKA0KICBzdm0sIA0KICB0cmFpbi54ID0gWC50cmFpbiwgDQogIHRyYWluLnkgPSB5LnRyYWluLCANCiAgcmFuZ2VzID0gbGlzdChlcHNpbG9uID0gc2VxKDAuMSwgMC41LCAwLjEpLCANCiAgICAgICAgICAgICAgICBjb3N0ID0gYygxLCAxMCwgMTAwKSwgDQogICAgICAgICAgICAgICAgZ2FtbWEgPSBjKDAuMDEsIDAuMSwgMSkpLCAgIyBIeXBlcnBhcmFtZXRlcnMgZm9yIFJCRiBrZXJuZWwNCiAgdHVuZWNvbnRyb2wgPSB0dW5lLmNvbnRyb2woc2FtcGxpbmcgPSAiY3Jvc3MiLCBjcm9zcyA9IDUpICAjIDUtZm9sZCBjcm9zcy12YWxpZGF0aW9uDQopDQoNCiMgQ2hlY2sgdGhlIGJlc3QgcGFyYW1ldGVycyBmb3VuZA0KcHJpbnQodHVuZS5SQkYkYmVzdC5wYXJhbWV0ZXJzKQ0KDQojIFRyYWluIHRoZSBmaW5hbCBtb2RlbCB1c2luZyB0aGUgYmVzdCBwYXJhbWV0ZXJzIGZvciBSQkYga2VybmVsDQpmaW5hbC5SQkYgPC0gc3ZtKA0KICBYLnRyYWluLCB5LnRyYWluLCANCiAgdHlwZSA9ICJlcHMtcmVncmVzc2lvbiIsICAjICJlcHMtcmVncmVzc2lvbiIgZm9yIFNWUg0KICBrZXJuZWwgPSAicmFkaWFsIiwgDQogIGVwc2lsb24gPSB0dW5lLlJCRiRiZXN0LnBhcmFtZXRlcnMkZXBzaWxvbiwgDQogIGNvc3QgPSB0dW5lLlJCRiRiZXN0LnBhcmFtZXRlcnMkY29zdCwgDQogIGdhbW1hID0gdHVuZS5SQkYkYmVzdC5wYXJhbWV0ZXJzJGdhbW1hDQopDQoNCiMgTWFrZSBwcmVkaWN0aW9ucyBvbiB0aGUgdGVzdCBzZXQNCnByZWQuUkJGIDwtIHByZWRpY3QoZmluYWwuUkJGLCBYLnRlc3QpDQoNCiMgRXZhbHVhdGUgcGVyZm9ybWFuY2UgKE1lYW4gU3F1YXJlZCBFcnJvciBhbmQgTWVhbiBBYnNvbHV0ZSBFcnJvcikNCm1zZS5SQkYgPC0gbWVhbigoeS50ZXN0IC0gcHJlZC5SQkYpXjIpICAgICMgTWVhbiBzcXVhcmVkIGVycm9yDQptYWUuUkJGIDwtIG1lYW4oYWJzKHkudGVzdCAtIHByZWQuUkJGKSkgICAjIE1lYW4gYWJzb2x1dGUgZXJyb3INCg0KIyBQcmludCB0aGUgcGVyZm9ybWFuY2UgbWV0cmljcw0KcHJpbnQocGFzdGUoIk1TRSBmb3IgUkJGIEtlcm5lbDoiLCBtc2UuUkJGKSkNCnByaW50KHBhc3RlKCJNQUUgZm9yIFJCRiBLZXJuZWw6IiwgbWFlLlJCRikpDQoNCiMgT3B0aW9uYWxseSwgeW91IGNhbiBwZXJmb3JtIHNpbWlsYXIgc3RlcHMgZm9yIHRoZSBsaW5lYXIga2VybmVsIChpZiBuZWVkZWQpDQojIFBlcmZvcm0gZ3JpZCBzZWFyY2ggZm9yIGh5cGVycGFyYW1ldGVyIHR1bmluZzogTGluZWFyIGtlcm5lbA0KdHVuZS5saW4gPC0gdHVuZSgNCiAgc3ZtLCANCiAgdHJhaW4ueCA9IFgudHJhaW4sIA0KICB0cmFpbi55ID0geS50cmFpbiwgDQogIHJhbmdlcyA9IGxpc3QoZXBzaWxvbiA9IHNlcSgwLjEsIDAuNSwgMC4xKSwgDQogICAgICAgICAgICAgICAgY29zdCA9IGMoMSwgMTAsIDEwMCkpLCAgIyBIeXBlcnBhcmFtZXRlcnMgZm9yIExpbmVhciBrZXJuZWwNCiAgdHVuZWNvbnRyb2wgPSB0dW5lLmNvbnRyb2woc2FtcGxpbmcgPSAiY3Jvc3MiLCBjcm9zcyA9IDUpICAjIDUtZm9sZCBjcm9zcy12YWxpZGF0aW9uDQopDQoNCiMgQ2hlY2sgdGhlIGJlc3QgcGFyYW1ldGVycyBmb3IgTGluZWFyIGtlcm5lbA0KcHJpbnQodHVuZS5saW4kYmVzdC5wYXJhbWV0ZXJzKQ0KDQojIFRyYWluIHRoZSBmaW5hbCBtb2RlbCB1c2luZyB0aGUgYmVzdCBwYXJhbWV0ZXJzIGZvciBMaW5lYXIga2VybmVsDQpmaW5hbC5saW4gPC0gc3ZtKA0KICBYLnRyYWluLCB5LnRyYWluLCANCiAgdHlwZSA9ICJlcHMtcmVncmVzc2lvbiIsICAjICJlcHMtcmVncmVzc2lvbiIgZm9yIFNWUg0KICBrZXJuZWwgPSAibGluZWFyIiwgDQogIGVwc2lsb24gPSB0dW5lLmxpbiRiZXN0LnBhcmFtZXRlcnMkZXBzaWxvbiwgDQogIGNvc3QgPSB0dW5lLmxpbiRiZXN0LnBhcmFtZXRlcnMkY29zdA0KKQ0KDQojIE1ha2UgcHJlZGljdGlvbnMgb24gdGhlIHRlc3Qgc2V0DQpwcmVkLmxpbiA8LSBwcmVkaWN0KGZpbmFsLmxpbiwgWC50ZXN0KQ0KDQojIEV2YWx1YXRlIHBlcmZvcm1hbmNlIGZvciBMaW5lYXIga2VybmVsDQptc2UubGluIDwtIG1lYW4oKHkudGVzdCAtIHByZWQubGluKV4yKSAgICAjIE1lYW4gc3F1YXJlZCBlcnJvcg0KbWFlLmxpbiA8LSBtZWFuKGFicyh5LnRlc3QgLSBwcmVkLmxpbikpICAgIyBNZWFuIGFic29sdXRlIGVycm9yDQoNCiMgUHJpbnQgdGhlIHBlcmZvcm1hbmNlIG1ldHJpY3MgZm9yIExpbmVhciBrZXJuZWwNCnByaW50KHBhc3RlKCJNU0UgZm9yIExpbmVhciBLZXJuZWw6IiwgbXNlLmxpbikpDQpwcmludChwYXN0ZSgiTUFFIGZvciBMaW5lYXIgS2VybmVsOiIsIG1hZS5saW4pKQ0KDQpgYGANCmBgYHtyLCB3YXJuaW5ncz1GQUxTRX0NCiMgTG9hZCBuZWNlc3NhcnkgbGlicmFyaWVzDQpsaWJyYXJ5KE1BU1MpICAjIEZvciBzdGVwQUlDKCkNCg0KIyBGaXQgdGhlIGluaXRpYWwgT0xTIG1vZGVsICh1c2luZyBhbGwgcHJlZGljdG9ycykgDQpsc2UuZml0IDwtIGxtKGdsdWNvc2UgfiAuLCBkYXRhID0gaGVhcnRzX2ZpbmFsKQ0KDQojIEFwcGx5IHN0ZXB3aXNlIEFJQyBtb2RlbCBzZWxlY3Rpb24gKGJvdGggZGlyZWN0aW9uczogZm9yd2FyZCBhbmQgYmFja3dhcmQpDQpBSUMuZml0IDwtIHN0ZXBBSUMobHNlLmZpdCwgZGlyZWN0aW9uID0gImJvdGgiLCB0cmFjZSA9IEZBTFNFKQ0KDQojIFNwbGl0IHRoZSBkYXRhIGludG8gZmVhdHVyZXMgKFgpIGFuZCB0YXJnZXQgKHkpICg4MC0yMCBzcGxpdCBhcyBiZWZvcmUpDQpzZXQuc2VlZCgxMjMpDQp0cmFpbi5pbmRleCA8LSBzYW1wbGUoMTpucm93KGhlYXJ0c19maW5hbCksIDAuOCAqIG5yb3coaGVhcnRzX2ZpbmFsKSkNClgudHJhaW4gPC0gaGVhcnRzX2ZpbmFsW3RyYWluLmluZGV4LCAtd2hpY2gobmFtZXMoaGVhcnRzX2ZpbmFsKSA9PSAiZ2x1Y29zZSIpXQ0KeS50cmFpbiA8LSBoZWFydHNfZmluYWwkZ2x1Y29zZVt0cmFpbi5pbmRleF0NClgudGVzdCA8LSBoZWFydHNfZmluYWxbLXRyYWluLmluZGV4LCAtd2hpY2gobmFtZXMoaGVhcnRzX2ZpbmFsKSA9PSAiZ2x1Y29zZSIpXQ0KeS50ZXN0IDwtIGhlYXJ0c19maW5hbCRnbHVjb3NlWy10cmFpbi5pbmRleF0NCg0KIyBNYWtlIHByZWRpY3Rpb25zIG9uIHRoZSB0ZXN0IHNldCB1c2luZyB0aGUgc3RlcHdpc2Utc2VsZWN0ZWQgbW9kZWwNCnByZWQubHNlIDwtIHByZWRpY3QoQUlDLmZpdCwgbmV3ZGF0YSA9IFgudGVzdCkNCg0KIyBDYWxjdWxhdGUgTWVhbiBTcXVhcmVkIEVycm9yIChNU0UpIGFuZCBNZWFuIEFic29sdXRlIEVycm9yIChNQUUpDQptc2UubHNlIDwtIG1lYW4oKHkudGVzdCAtIHByZWQubHNlKV4yKSAgICAjIE1lYW4gc3F1YXJlZCBlcnJvcg0KbWFlLmxzZSA8LSBtZWFuKGFicyh5LnRlc3QgLSBwcmVkLmxzZSkpICAgIyBNZWFuIGFic29sdXRlIGVycm9yDQoNCiMgUHJpbnQgdGhlIHBlcmZvcm1hbmNlIG1ldHJpY3MNCnByaW50KHBhc3RlKCJNU0UgZm9yIE9MUyBtb2RlbDoiLCBtc2UubHNlKSkNCnByaW50KHBhc3RlKCJNQUUgZm9yIE9MUyBtb2RlbDoiLCBtYWUubHNlKSkNCg0KIyBEaWFnbm9zdGljIHBsb3RzIGZvciB0aGUgZmluYWwgbW9kZWwNCnBhcihtZnJvdyA9IGMoMiwgMiksIG1hciA9IGMoMiwgMiwgMiwgMikpDQpwbG90KEFJQy5maXQpDQoNCg0KYGBgDQoNClRoZSByZXNpZHVhbCBwbG90ICh0b3AgbGVmdCBwYW5lbCkgc2hvd3Mgbm8gY3VydmUgcGF0dGVybnMgaW4gdGhlIGRhdGEsIG1lYW5pbmcgdGhhdCBpdCBkb2VzIGhhdmUgYSBsaW5lYXIgcmVncmVzc2lvbi4gVGhlcmVmb3JlLCB3ZSBkbyBub3QgbmVlZCB0byByZWZpdCB0aGUgbW9kZWwuIA0KDQpOZXh0LCB3ZSBjYWxjdWxhdGUgdGhlIHByZWRpY3RpdmUgZXJyb3JzIG9mIHRoZSBjYW5kaWRhdGUgbW9kZWxzIGluIHRoZSBmb2xsb3dpbmcgdGFibGUuDQoNCmBgYHtyLCB3YXJuaW5ncz1GQUxTRX0NClBlcmZvcm1hbmNlIDwtIGRhdGEuZnJhbWUoUkJGLlNWUj1jKG1zZS5SQkYsIG1hZS5SQkYpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBMaW5lYXIuU1ZSID0gYyhtc2UubGluLCBtYWUubGluKSkNCnJvdy5uYW1lcyhQZXJmb3JtYW5jZSkgPC0gYygiTVNFIiwgIk1BRSIpDQojIw0KcGFuZGVyKFBlcmZvcm1hbmNlKQ0KDQpgYGANClRoZSBhYm92ZSBwcmVkaWN0aXZlIGVycm9ycyBzaG93IHRoYXQgdGhlIGxpbmVhciBzdXBwb3J0IHZlY3RvciBtYWNoaW5lIG91dHBlcmZvcm1zIGxpbmVhciBrZXJuZWwgYmFzZWQgU1ZSIHJlZ3Jlc3Npb24gbW9kZWxzLg0KDQojIyBTVk0gZm9yIEJpbmFyeSBDbGFzc2lmY2F0aW9uDQoNCldoZW4gaXQgY29tZXMgdG8gYW5zd2VyaW5nIHRoZSBxdWVzdGlvbiBpZiBzb21lb25lIHdpbGwgaGF2ZSBjb3JvbmFyeSBoZWFydCBkaXNlYXNlIGluIHRlbiB5ZWFycyAoVGVuWWVhcnNDSEQpIHdlIGNhbiB1c2UgYSBTVk0gdG8gZmluZCBhIGh5cGVycGxhbmUgdGhhdCBtYXhpbWl6ZXMgdGhlIG1hcmdpbiBiZXR3ZWVuIHRoZSB0d28gY2xhc3Nlcy4NCg0KDQpgYGB7ciwgd2FybmluZ3M9RkFMU0V9DQojIExvYWQgbmVjZXNzYXJ5IGxpYnJhcmllcw0KbGlicmFyeShlMTA3MSkgICAgIyBGb3Igc3ZtKCkNCg0KIyBUd28td2F5IGRhdGEgc3BsaXR0aW5nOiBUcmFpbiAoNzAlKSBhbmQgVGVzdCAoMzAlKQ0Kc2V0LnNlZWQoMTIzKSAgIyBGb3IgcmVwcm9kdWNpYmlsaXR5DQppbmRleCA8LSBzYW1wbGUoMTpucm93KGhlYXJ0c19maW5hbCksIDAuNyAqIG5yb3coaGVhcnRzX2ZpbmFsKSkNCnRyYWluLmRhdGEgPC0gaGVhcnRzX2ZpbmFsW2luZGV4LCBdDQp0ZXN0LmRhdGEgPC0gaGVhcnRzX2ZpbmFsWy1pbmRleCwgXQ0KDQojIFNldCB1cCBjdXN0b20gY3Jvc3MtdmFsaWRhdGlvbiBjb250cm9sICg1LWZvbGQgY3Jvc3MtdmFsaWRhdGlvbikNCnR1bmVfY29udHJvbCA8LSB0dW5lLmNvbnRyb2woDQogIGNyb3NzID0gNSwgICMgVXNlIDUtZm9sZCBjcm9zcy12YWxpZGF0aW9uDQogIG5yZXBlYXQgPSAxICMgTnVtYmVyIG9mIHJlcGV0aXRpb25zIChmb3IgcmVwZWF0ZWQgY3Jvc3MtdmFsaWRhdGlvbikNCikNCg0KIyBQZXJmb3JtIGEgZ3JpZCBzZWFyY2ggZm9yIHRoZSBiZXN0IGh5cGVycGFyYW1ldGVycyBmb3IgdGhlIFJCRiBrZXJuZWwNCnR1bmUuUkJGIDwtIHR1bmUoDQogIHN2bSwgICAgICAgICAgICAgIyBTVk0gYWxnb3JpdGhtIGZvciB0dW5pbmcNCiAgVGVuWWVhckNIRCB+IC4sICAjIFVzZSBUZW5ZZWFyQ0hEIGFzIHRoZSB0YXJnZXQgdmFyaWFibGUNCiAgZGF0YSA9IHRyYWluLmRhdGEsDQogIGtlcm5lbCA9ICJyYWRpYWwiLCAgIyBSYWRpYWwgYmFzaXMgZnVuY3Rpb24ga2VybmVsDQogIHJhbmdlcyA9IGxpc3QoDQogICAgY29zdCA9IDEwXigtMToyKSwgICAjIFR1bmluZyBoeXBlcnBhcmFtZXRlciBDIGluIHRoZSBsb3NzIGZ1bmN0aW9uDQogICAgZ2FtbWEgPSBjKDAuMSwgMC41LCAxLCAyKSAgIyBIeXBlcnBhcmFtZXRlciBnYW1tYSBmb3IgdGhlIFJCRiBrZXJuZWwNCiAgKSwNCiAgdHVuZWNvbnRyb2wgPSB0dW5lX2NvbnRyb2wgICMgVXNlIHRoZSBkZWZpbmVkIGNyb3NzLXZhbGlkYXRpb24gc2V0dGluZ3MNCikNCg0KIyBQcmludCB0aGUgdHVuaW5nIHJlc3VsdHMgZm9yIGluc3BlY3Rpb24NCiMgcHJpbnQodHVuZS5SQkYpDQoNCiMgRXh0cmFjdCB0aGUgYmVzdCBtb2RlbCBhbmQgaHlwZXJwYXJhbWV0ZXJzDQpiZXN0LlJCRiA8LSB0dW5lLlJCRiRiZXN0Lm1vZGVsDQpiZXN0LmNvc3QuUkJGIDwtIGJlc3QuUkJGJGNvc3QNCmJlc3QuZ2FtbWEuUkJGIDwtIGJlc3QuUkJGJGdhbW1hDQoNCiMgUHJpbnQgdGhlIGJlc3QgaHlwZXJwYXJhbWV0ZXJzDQpjYXQoIkJlc3QgQ29zdDoiLCBiZXN0LmNvc3QuUkJGLCAiXG4iKQ0KY2F0KCJCZXN0IEdhbW1hOiIsIGJlc3QuZ2FtbWEuUkJGLCAiXG4iKQ0KDQojIFRyYWluIHRoZSBmaW5hbCBTVk0gbW9kZWwgd2l0aCB0aGUgYmVzdCBoeXBlcnBhcmFtZXRlcnMNCmZpbmFsLlJCRi5jbGFzcyA8LSBzdm0oDQogIFRlblllYXJDSEQgfiAuLCAgICAjIFRhcmdldCB2YXJpYWJsZTogVGVuWWVhckNIRA0KICBkYXRhID0gdHJhaW4uZGF0YSwgICMgVHJhaW5pbmcgZGF0YQ0KICBrZXJuZWwgPSAicmFkaWFsIiwgICMgUmFkaWFsIGtlcm5lbA0KICBjb3N0ID0gYmVzdC5jb3N0LlJCRiwgICMgQmVzdCBjb3N0IGZyb20gdHVuaW5nDQogIGdhbW1hID0gYmVzdC5nYW1tYS5SQkYgICMgQmVzdCBnYW1tYSBmcm9tIHR1bmluZw0KKQ0KDQojIFByaW50IHRoZSBmaW5hbCBtb2RlbCBmb3IgaW5zcGVjdGlvbg0KIyBwcmludChmaW5hbC5SQkYuY2xhc3MpDQoNCiMgTWFrZSBwcmVkaWN0aW9ucyBvbiB0aGUgdGVzdCBzZXQNCnByZWQuUkJGLmNsYXNzIDwtIHByZWRpY3QoZmluYWwuUkJGLmNsYXNzLCB0ZXN0LmRhdGEsIHR5cGUgPSAiY2xhc3MiKQ0KDQojIEV2YWx1YXRlIHRoZSBtb2RlbCB1c2luZyBhIGNvbmZ1c2lvbiBtYXRyaXgNCmNvbmZ1c2lvbi5tYXRyaXguUkJGIDwtIHRhYmxlKFByZWRpY3RlZCA9IHByZWQuUkJGLmNsYXNzLCBBY3R1YWwgPSB0ZXN0LmRhdGEkVGVuWWVhckNIRCkNCg0KIyBQcmludCB0aGUgY29uZnVzaW9uIG1hdHJpeA0KcHJpbnQoY29uZnVzaW9uLm1hdHJpeC5SQkYpDQoNCiMgT3B0aW9uYWxseSwgY2FsY3VsYXRlIGFjY3VyYWN5IG9yIG90aGVyIG1ldHJpY3MgKGUuZy4sIHByZWNpc2lvbiwgcmVjYWxsKQ0KYWNjdXJhY3kgPC0gc3VtKGRpYWcoY29uZnVzaW9uLm1hdHJpeC5SQkYpKSAvIHN1bShjb25mdXNpb24ubWF0cml4LlJCRikNCmNhdCgiQWNjdXJhY3k6IiwgYWNjdXJhY3ksICJcbiIpDQoNCmBgYA0KYGBge3IsIHdhcm5pbmdzPUZBTFNFfQ0KDQojIENhbGN1bGF0ZSBhY2N1cmFjeQ0KYWNjdXJhY3kgPC0gc3VtKGRpYWcoY29uZnVzaW9uLm1hdHJpeC5SQkYpKSAvIHN1bShjb25mdXNpb24ubWF0cml4LlJCRikNCmNhdCgiXG5cbiBBY2N1cmFjeToiLCBhY2N1cmFjeSwgIlxuIikNCg0KYGBgDQpOZXh0LCB3ZSBhc3Nlc3MgdGhlIGdsb2JhbCBwZXJmb3JtYW5jZSB0aHJvdWdoIFJPQyBhbmFseXNpcy4gU2luY2UgUk9DIGFuYWx5c2lzIGlzIHVzdWFsbHkgdXNlZCB0byBjb21wYXJlIHR3byBvciBtb3JlIGJpbmFyeSBjbGFzc2lmaWNhdGlvbiBtb2RlbHMsIHdlIHdpbGwgYnVpbGQgdHdvIFNWTSBtb2RlbHMgd2l0aCBsaW5lYXIgYW5kIFJCRiBrZXJuZWxzIHJlc3BlY3RpdmVseSwgYW5kIHRoZSBzdGFuZGFyZCBiaW5hcnkgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbHMgYW5kIHRoZW4gY29tcGFyZSB0aGUgdGhyZWUgY2FuZGlkYXRlIGNsYXNzaWZpY2F0aW9uIG1vZGVscyB1c2luZyBST0MgYW5kIEFVQy4NCg0KYGBge3IsIHdhcm5pbmdzPUZBTFNFfQ0KDQojIExvYWQgbmVjZXNzYXJ5IGxpYnJhcmllcw0KbGlicmFyeShlMTA3MSkgICMgRm9yIHN2bSgpDQpsaWJyYXJ5KHBST0MpICAgIyBGb3IgUk9DIGN1cnZlDQpsaWJyYXJ5KE1BU1MpICAgIyBGb3Igc3RlcEFJQygpDQojIEFzc3VtaW5nIHRoZSAnaGVhcnRzX2ZpbmFsJyBkYXRhc2V0IGlzIGFscmVhZHkgbG9hZGVkDQoNCiMgU3BsaXQgdGhlIGRhdGEgaW50byB0cmFpbmluZyAoNzAlKSBhbmQgdGVzdGluZyAoMzAlKSBzZXRzDQpzZXQuc2VlZCgxMjMpICAjIEZvciByZXByb2R1Y2liaWxpdHkNCmluZGV4IDwtIHNhbXBsZSgxOm5yb3coaGVhcnRzX2ZpbmFsKSwgMC43ICogbnJvdyhoZWFydHNfZmluYWwpKQ0KdHJhaW4uZGF0YSA8LSBoZWFydHNfZmluYWxbaW5kZXgsIF0NCnRlc3QuZGF0YSA8LSBoZWFydHNfZmluYWxbLWluZGV4LCBdDQoNCiMgU2V0IHVwIGN1c3RvbSBjcm9zcy12YWxpZGF0aW9uIGNvbnRyb2wgKDUtZm9sZCBjcm9zcy12YWxpZGF0aW9uKQ0KdHVuZS5jb250cm9sIDwtIHR1bmUuY29udHJvbCgNCiAgY3Jvc3MgPSA1LCAgIyA1LWZvbGQgY3Jvc3MtdmFsaWRhdGlvbg0KICBucmVwZWF0ID0gMSAjIE51bWJlciBvZiByZXBldGl0aW9ucyAoZm9yIHJlcGVhdGVkIGNyb3NzLXZhbGlkYXRpb24pDQopDQoNCiMjIExpbmVhciBTVk0gR3JpZCBTZWFyY2ggZm9yIEJlc3QgSHlwZXJwYXJhbWV0ZXJzDQp0dW5lLmxpbiA8LSB0dW5lKA0KICBzdm0sICAgICAgICAgICAgICAjIFNWTSBhbGdvcml0aG0NCiAgVGVuWWVhckNIRCB+IC4sICAgIyBUYXJnZXQgdmFyaWFibGUgaXMgJ1RlblllYXJDSEQnDQogIGRhdGEgPSB0cmFpbi5kYXRhLA0KICBrZXJuZWwgPSAibGluZWFyIiwgIyBMaW5lYXIga2VybmVsDQogIHJhbmdlcyA9IGxpc3QoDQogICAgY29zdCA9IDEwXigtMToyKSAgICMgVHVuaW5nIGh5cGVycGFyYW1ldGVyICdDJyBpbiB0aGUgbG9zcyBmdW5jdGlvbg0KICApLA0KICB0dW5lY29udHJvbCA9IHR1bmUuY29udHJvbCAgIyBVc2UgY3VzdG9tIGNyb3NzLXZhbGlkYXRpb24gc2V0dGluZ3MNCikNCg0KIyBFeHRyYWN0IHRoZSBiZXN0IG1vZGVsIGFuZCBoeXBlcnBhcmFtZXRlcnMgZm9yIExpbmVhciBTVk0NCmJlc3QubGluIDwtIHR1bmUubGluJGJlc3QubW9kZWwNCmJlc3QuY29zdC5saW4gPC0gYmVzdC5saW4kY29zdA0KDQojIFRyYWluIHRoZSBmaW5hbCBMaW5lYXIgU1ZNIG1vZGVsIHdpdGggdGhlIGJlc3QgaHlwZXJwYXJhbWV0ZXJzDQpmaW5hbC5saW4gPC0gc3ZtKA0KICBUZW5ZZWFyQ0hEIH4gLiwgICAgICAgICMgVGFyZ2V0IHZhcmlhYmxlOiAnVGVuWWVhckNIRCcNCiAgZGF0YSA9IHRyYWluLmRhdGEsICAgICAjIFRyYWluaW5nIGRhdGENCiAga2VybmVsID0gImxpbmVhciIsICAgICAjIExpbmVhciBrZXJuZWwNCiAgY29zdCA9IGJlc3QuY29zdC5saW4sICAjIEJlc3QgY29zdCBmcm9tIHR1bmluZw0KICBwcm9iYWJpbGl0eSA9IFRSVUUgICAgICMgUmVxdWVzdCBwcm9iYWJpbGl0eSBlc3RpbWF0ZXMNCikNCg0KIyMgUmFkaWFsIFNWTSBHcmlkIFNlYXJjaCBmb3IgQmVzdCBIeXBlcnBhcmFtZXRlcnMNCnR1bmUuUkJGIDwtIHR1bmUoDQogIHN2bSwgICAgICAgICAgICAgICMgU1ZNIGFsZ29yaXRobQ0KICBUZW5ZZWFyQ0hEIH4gLiwgICAjIFRhcmdldCB2YXJpYWJsZSBpcyAnVGVuWWVhckNIRCcNCiAgZGF0YSA9IHRyYWluLmRhdGEsDQogIGtlcm5lbCA9ICJyYWRpYWwiLCAjIFJhZGlhbCBrZXJuZWwNCiAgcmFuZ2VzID0gbGlzdCgNCiAgICBjb3N0ID0gMTBeKC0xOjIpLCAgIyBUdW5pbmcgaHlwZXJwYXJhbWV0ZXIgJ0MnDQogICAgZ2FtbWEgPSBjKDAuMSwgMC41LCAxLCAyKSAgIyBUdW5pbmcgJ2dhbW1hJyBmb3IgdGhlIHJhZGlhbCBrZXJuZWwNCiAgKSwNCiAgdHVuZWNvbnRyb2wgPSB0dW5lLmNvbnRyb2wgICMgQ3VzdG9tIGNyb3NzLXZhbGlkYXRpb24gc2V0dGluZ3MNCikNCg0KIyBFeHRyYWN0IHRoZSBiZXN0IG1vZGVsIGFuZCBoeXBlcnBhcmFtZXRlcnMgZm9yIFJhZGlhbCBTVk0NCmJlc3QuUkJGIDwtIHR1bmUuUkJGJGJlc3QubW9kZWwNCmJlc3QuY29zdC5SQkYgPC0gYmVzdC5SQkYkY29zdA0KYmVzdC5nYW1tYS5SQkYgPC0gYmVzdC5SQkYkZ2FtbWENCg0KIyBUcmFpbiB0aGUgZmluYWwgUmFkaWFsIFNWTSBtb2RlbCB3aXRoIHRoZSBiZXN0IGh5cGVycGFyYW1ldGVycw0KZmluYWwuUkJGIDwtIHN2bSgNCiAgVGVuWWVhckNIRCB+IC4sICAgICAgICAjIFRhcmdldCB2YXJpYWJsZTogJ1RlblllYXJDSEQnDQogIGRhdGEgPSB0cmFpbi5kYXRhLCAgICAgIyBUcmFpbmluZyBkYXRhDQogIGtlcm5lbCA9ICJyYWRpYWwiLCAgICAgIyBSYWRpYWwga2VybmVsDQogIGNvc3QgPSBiZXN0LmNvc3QuUkJGLCAgIyBCZXN0IGNvc3QgZnJvbSB0dW5pbmcNCiAgZ2FtbWEgPSBiZXN0LmdhbW1hLlJCRiwgICMgQmVzdCBnYW1tYSBmcm9tIHR1bmluZw0KICBwcm9iYWJpbGl0eSA9IFRSVUUgICAgICMgUmVxdWVzdCBwcm9iYWJpbGl0eSBlc3RpbWF0ZXMNCikNCg0KIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KIyMjIExvZ2lzdGljIFJlZ3Jlc3Npb24gTW9kZWwNCmxvZ2l0LmZpdCA8LSBnbG0oVGVuWWVhckNIRCB+IC4sIGRhdGEgPSB0cmFpbi5kYXRhLCBmYW1pbHkgPSBiaW5vbWlhbCkNCkFJQy5sb2dpdCA8LSBzdGVwKGxvZ2l0LmZpdCwgZGlyZWN0aW9uID0gImJvdGgiLCB0cmFjZSA9IDApICAjIFN0ZXB3aXNlIHNlbGVjdGlvbg0KcHJlZC5sb2dpdCA8LSBwcmVkaWN0KEFJQy5sb2dpdCwgdGVzdC5kYXRhLCB0eXBlID0gInJlc3BvbnNlIikNCg0KIyMjIyMjIyMjIyMjIyMjIyMjIw0KIyBST0MgQ3VydmUgYW5kIEFVQyBmb3IgdGhlIG1vZGVscw0KDQojIEdldCB0aGUgcHJlZGljdGVkIHByb2JhYmlsaXRpZXMgZm9yIExpbmVhciBTVk0sIFJhZGlhbCBTVk0sIGFuZCBMb2dpc3RpYyBSZWdyZXNzaW9uDQpwcmVkLnByb2IubGluIDwtIHByZWRpY3QoZmluYWwubGluLCB0ZXN0LmRhdGEsIHByb2JhYmlsaXR5ID0gVFJVRSkNCnByZWQucHJvYi5SQkYgPC0gcHJlZGljdChmaW5hbC5SQkYsIHRlc3QuZGF0YSwgcHJvYmFiaWxpdHkgPSBUUlVFKQ0KDQojIEV4dHJhY3RpbmcgdGhlIHByb2JhYmlsaXRpZXMgZm9yIHRoZSBwb3NpdGl2ZSBjbGFzcw0KcHJvYi5saW5lYXIgPC0gYXR0cihwcmVkLnByb2IubGluLCAicHJvYmFiaWxpdGllcyIpWywgMl0NCnByb2IucmFkaWFsIDwtIGF0dHIocHJlZC5wcm9iLlJCRiwgInByb2JhYmlsaXRpZXMiKVssIDJdDQoNCiMgQ29tcHV0ZSBST0MgY3VydmVzDQpyb2NfbGluIDwtIHJvYyh0ZXN0LmRhdGEkVGVuWWVhckNIRCwgcHJvYi5saW5lYXIpDQpyb2NfUkJGIDwtIHJvYyh0ZXN0LmRhdGEkVGVuWWVhckNIRCwgcHJvYi5yYWRpYWwpDQpyb2NfbG9naXQgPC0gcm9jKHRlc3QuZGF0YSRUZW5ZZWFyQ0hELCBwcmVkLmxvZ2l0KQ0KDQojIFNlbnNpdGl2aXR5IGFuZCBTcGVjaWZpY2l0eSBmb3IgZWFjaCBtb2RlbA0KbGluLnNlbiA8LSByb2NfbGluJHNlbnNpdGl2aXRpZXMNCmxpbi5zcGUgPC0gcm9jX2xpbiRzcGVjaWZpY2l0aWVzDQpyYWQuc2VuIDwtIHJvY19SQkYkc2Vuc2l0aXZpdGllcw0KcmFkLnNwZSA8LSByb2NfUkJGJHNwZWNpZmljaXRpZXMNCmxvZ2l0LnNlbiA8LSByb2NfbG9naXQkc2Vuc2l0aXZpdGllcw0KbG9naXQuc3BlIDwtIHJvY19sb2dpdCRzcGVjaWZpY2l0aWVzDQoNCiMgQVVDIHZhbHVlcw0KYXVjLmxpbiA8LSByb2NfbGluJGF1Yw0KYXVjLnJhZCA8LSByb2NfUkJGJGF1Yw0KYXVjLmxvZ2l0IDwtIHJvY19sb2dpdCRhdWMNCg0KIyBQbG90IFJPQyBjdXJ2ZXMNCnBsb3QoMSAtIGxpbi5zcGUsIGxpbi5zZW4sICANCiAgICAgeGxhYiA9ICIxIC0gU3BlY2lmaWNpdHkiLA0KICAgICB5bGFiID0gIlNlbnNpdGl2aXR5IiwNCiAgICAgY29sID0gImRhcmtyZWQiLA0KICAgICB0eXBlID0gImwiLA0KICAgICBsdHkgPSAxLA0KICAgICBsd2QgPSAxLA0KICAgICBtYWluID0gIlJPQyBDdXJ2ZXMgb2YgU1ZNIGFuZCBMb2dpc3RpYyBSZWdyZXNzaW9uIikNCmxpbmVzKDEgLSByYWQuc3BlLCByYWQuc2VuLCANCiAgICAgIGNvbCA9ICJibHVlIiwNCiAgICAgIGx0eSA9IDEsDQogICAgICBsd2QgPSAxKQ0KbGluZXMoMSAtIGxvZ2l0LnNwZSwgbG9naXQuc2VuLCAgICAgIA0KICAgICAgY29sID0gIm9yYW5nZSIsDQogICAgICBsdHkgPSAxLA0KICAgICAgbHdkID0gMSkNCg0KIyBBZGQgdGhlIGRpYWdvbmFsIGxpbmUgZm9yIHJhbmRvbSBndWVzc2luZw0KYWJsaW5lKDAsIDEsIGNvbCA9ICJza3libHVlMyIsIGx0eSA9IDIsIGx3ZCA9IDIpDQoNCiMgQWRkIHZlcnRpY2FsIGxpbmVzIGZvciB0aHJlc2hvbGRzDQphYmxpbmUodiA9IGMoMC4wNDksIDAuMTUxKSwgbHR5ID0gMywgY29sID0gImRhcmtncmVlbiIpDQoNCiMgTGVnZW5kIGZvciB0aGUgcGxvdA0KbGVnZW5kKCJib3R0b21yaWdodCIsIGMoIkxpbmVhciBTVk0iLCAiUmFkaWFsIFNWTSIsICJMb2dpc3RpYyBSZWdyZXNzaW9uIiksDQogICAgICAgbHR5ID0gYygxLCAxLCAxKSwgbHdkID0gcmVwKDEsIDMpLA0KICAgICAgIGNvbCA9IGMoInJlZCIsICJibHVlIiwgIm9yYW5nZSIpLA0KICAgICAgIGJ0eSA9ICJuIiwgY2V4ID0gMC44KQ0KDQojIEFubm90YXRlIHdpdGggQVVDIHZhbHVlcw0KdGV4dCgwLjgsIDAuNDYsIHBhc3RlKCJMaW5lYXIgQVVDOiAiLCByb3VuZChhdWMubGluLCA0KSksIGNleCA9IDAuOCkNCnRleHQoMC44LCAwLjQsIHBhc3RlKCJSYWRpYWwgQVVDOiAiLCByb3VuZChhdWMucmFkLCA0KSksIGNleCA9IDAuOCkNCnRleHQoMC44LCAwLjM0LCBwYXN0ZSgiTG9naXN0aWMgQVVDOiAiLCByb3VuZChhdWMubG9naXQsIDQpKSwgY2V4ID0gMC44KQ0KDQoNCmBgYA0KDQpUaGUgUk9DIGN1cnZlIGFib3ZlIGluZGljYXRlcyB0aGF0IHRoZSBsaW5lYXIgU1ZNLCBSQkYgU1ZNLCBhbmQgc3RhbmRhcmQgbGluZWFyIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWxzIGRvIG5vdCBwZXJmb3JtIGVxdWFsbHkgd2VsbCBvbiBhIGdsb2JhbCBzY2FsZS4gSG93ZXZlciwgb25lIG5vdGFibGUgb2JzZXJ2YXRpb24gZnJvbSB0aGUgUk9DIGN1cnZlIGlzIHRoYXQgdGhlIHNlbnNpdGl2aXR5IG9mIGJvdGggdGhlIFJhZGlhbCBhbmQgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbHMgaXMgY29uc2lzdGVudGx5IGhpZ2hlciB0aGFuIHRoYXQgb2YgdGhlIGxpbmVhciBTVk0gd2hlbiB0aGUgc3BlY2lmaWNpdHkgbGV2ZWwgaXMgYmV0d2VlbiA4NSUgYW5kIDk1JS4gRm9yIHRoZSBxdWVzdGlvbiBvZiBjbGFzc2lmaWNhdGlvbiwgdGhlIGJlc3QgbW9kZWwgdG8gdXNlIGlzIHRoZSBsb2dpc3RpYyByZWdyZXNzaW9uDQoNCg0KDQojIyBDb25jbHVzaW9uDQoNCk92ZXJhbGwgaW4gdGhpcyBwcm9qZWN0IHdlIGhhdmUgZG9uZSBzb21lIEVEQSB3aXRoIGxpbmVhci9sb2dpc3RpY2FsIHJlZ3VsYXJpemVkIHJlZ3Jlc3Npb24sIGFzIHdlbGwgYXMgdXRpbGl6aW5nIFNWTXMgdG8gcHJlZGljdCBnbHVjb3NlIGxldmVscyBhbmQgbGFiZWwgaWYgc29tZW9uZSB3aWxsIGhhdmUgQ0hEIGluIHRlbiB5ZWFycy4gV2UgaGF2ZSBjcm9zcy12YWxpZGF0ZWQgdGhlc2UgZGlmZmVyZW50IG1vZGVscyB0byBzZWUgd2hpY2ggb25lIHBlcmZvcm1lZCB0aGUgYmVzdCBpbiBvcmRlciB0byBhbnN3ZXIgdGhlc2Uga2V5IHF1ZXN0aW9ucy4NCg0KICANCg0KDQoNCg0KDQoNCg0K